From 6ed84ebeda7704b605fa85c59f0857ffce2872c6 Mon Sep 17 00:00:00 2001 From: Eugene Manuilov Date: Fri, 13 Mar 2026 13:09:34 +0200 Subject: [PATCH 01/31] Add E2E tests proxy auth plugin to fake user authentication and granted scopes. --- .../docker/wordpress/plugins/proxy-auth.php | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 tests/playwright/docker/wordpress/plugins/proxy-auth.php diff --git a/tests/playwright/docker/wordpress/plugins/proxy-auth.php b/tests/playwright/docker/wordpress/plugins/proxy-auth.php new file mode 100644 index 00000000000..f3b02dd7d6e --- /dev/null +++ b/tests/playwright/docker/wordpress/plugins/proxy-auth.php @@ -0,0 +1,50 @@ +encrypt( 'test-access-token' ); + } +); + +/** + * Fake all required scopes have been granted. + */ +$_force_all_scopes = function () { + global $_force_all_scopes; + + // Remove the filter hook to prevent an infinite loop in the case where the `googlesitekit_auth_scopes` + // option is retrieved again during the call to `get_required_scopes()`. + remove_filter( 'get_user_option_googlesitekit_auth_scopes', $_force_all_scopes ); + + $required_scopes = ( new OAuth_Client( Plugin::instance()->context() ) )->get_required_scopes(); + + // Restore the filter hook for future calls to retrieve the option. + add_filter( 'get_user_option_googlesitekit_auth_scopes', $_force_all_scopes ); + + return $required_scopes; +}; + +// Ensure the filter hook is initially applied. +add_filter( 'get_user_option_googlesitekit_auth_scopes', $_force_all_scopes ); From 49d77ca14914dd256c030f5a89c9136d5847b929 Mon Sep 17 00:00:00 2001 From: Eugene Manuilov Date: Fri, 13 Mar 2026 19:30:22 +0200 Subject: [PATCH 02/31] Add support for enabling feature flags in Playwright E2E tests via annotations and a new mu-plugin. --- .../mu-plugins/e2e-feature-flags.php | 30 +++++++++++++++++++ tests/playwright/wordpress/cookies.ts | 12 ++++++++ tests/playwright/wordpress/options.ts | 21 +++++++++++-- tests/playwright/wordpress/plugins.ts | 4 +-- 4 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 tests/playwright/docker/wordpress/mu-plugins/e2e-feature-flags.php diff --git a/tests/playwright/docker/wordpress/mu-plugins/e2e-feature-flags.php b/tests/playwright/docker/wordpress/mu-plugins/e2e-feature-flags.php new file mode 100644 index 00000000000..dc40a9d6a5a --- /dev/null +++ b/tests/playwright/docker/wordpress/mu-plugins/e2e-feature-flags.php @@ -0,0 +1,30 @@ + type === '_wp:feature-flags' + ); + + if ( featureFlagsAnnotation?.description ) { + cookies.push( { + ...defaults, + name: '_wp_test_feature_flags', + value: featureFlagsAnnotation.description, + } ); + } + return this.context.addCookies( cookies ); } } diff --git a/tests/playwright/wordpress/options.ts b/tests/playwright/wordpress/options.ts index fc2f1f3bc3f..d51f1c6ca7c 100644 --- a/tests/playwright/wordpress/options.ts +++ b/tests/playwright/wordpress/options.ts @@ -20,11 +20,11 @@ import { TestDetailsAnnotation } from '@playwright/test'; /** - * Separator for plugin file paths. + * Separator for annotation values. * * @since n.e.x.t */ -export const PLUGINS_SEPARATOR = ','; +export const ANNOTATION_SEPARATOR = ','; /** * Sets the plugins to activate for the test. @@ -39,7 +39,22 @@ export function withPlugins( ...plugins: string[] ): TestDetailsAnnotation { type: '_wp:plugin', description: plugins .map( ( plugin ) => `google-site-kit-test-plugins/${ plugin }` ) - .join( PLUGINS_SEPARATOR ), + .join( ANNOTATION_SEPARATOR ), + }; +} + +/** + * Sets the feature flags to enable for the test. + * + * @since n.e.x.t + * + * @param {string[]} flags Feature flag names to enable. + * @return {TestDetailsAnnotation} The annotation to use for the test. + */ +export function withFeatureFlags( ...flags: string[] ): TestDetailsAnnotation { + return { + type: '_wp:feature-flags', + description: flags.join( ANNOTATION_SEPARATOR ), }; } diff --git a/tests/playwright/wordpress/plugins.ts b/tests/playwright/wordpress/plugins.ts index 2d04c474417..cb588678399 100644 --- a/tests/playwright/wordpress/plugins.ts +++ b/tests/playwright/wordpress/plugins.ts @@ -23,7 +23,7 @@ import { type Connection, type RowDataPacket } from 'mysql2/promise'; /** * Internal dependencies */ -import { PLUGINS_SEPARATOR } from './options'; +import { ANNOTATION_SEPARATOR } from './options'; /** * Serializes a string array to a PHP serialized format. @@ -188,7 +188,7 @@ export class WordPressPlugins { } description - .split( PLUGINS_SEPARATOR ) + .split( ANNOTATION_SEPARATOR ) .forEach( ( pluginFile: string ) => { pluginFiles.push( pluginFile ); } ); From 9c82fa817894ecc6b2db04a9354f33d0cc041eeb Mon Sep 17 00:00:00 2001 From: Eugene Manuilov Date: Fri, 13 Mar 2026 22:04:27 +0200 Subject: [PATCH 03/31] Add Playwright test for email reporting UI. --- .../docker/wordpress/plugins/proxy-auth.php | 19 ++++++++ .../email-reporting/email-reporting.spec.ts | 46 +++++++++++++++++++ tests/playwright/wordpress/wordpress.ts | 21 +++++++++ 3 files changed, 86 insertions(+) create mode 100644 tests/playwright/specs/email-reporting/email-reporting.spec.ts diff --git a/tests/playwright/docker/wordpress/plugins/proxy-auth.php b/tests/playwright/docker/wordpress/plugins/proxy-auth.php index f3b02dd7d6e..e18fcde5d92 100644 --- a/tests/playwright/docker/wordpress/plugins/proxy-auth.php +++ b/tests/playwright/docker/wordpress/plugins/proxy-auth.php @@ -11,6 +11,7 @@ use Google\Site_Kit\Core\Authentication\Clients\OAuth_Client; use Google\Site_Kit\Core\Storage\Data_Encryption; +use Google\Site_Kit\Modules\Search_Console\Settings as Search_Console_Settings; use Google\Site_Kit\Plugin; /** @@ -28,6 +29,24 @@ function () { } ); +/** + * Provide a placeholder site verification meta to fake an authenticated state. + */ +add_action( + 'init', + function () { + update_user_option( + get_current_user_id(), + 'googlesitekit_site_verified_meta', + 'verified' + ); + + $settings = get_option( Search_Console_Settings::OPTION ); + $settings['propertyID'] = 'http://localhost:9002'; + update_option( Search_Console_Settings::OPTION, $settings ); + } +); + /** * Fake all required scopes have been granted. */ diff --git a/tests/playwright/specs/email-reporting/email-reporting.spec.ts b/tests/playwright/specs/email-reporting/email-reporting.spec.ts new file mode 100644 index 00000000000..f0a63a19f86 --- /dev/null +++ b/tests/playwright/specs/email-reporting/email-reporting.spec.ts @@ -0,0 +1,46 @@ +/** + * Plugin activation tests. + * + * Site Kit by Google, Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Internal dependencies + */ +import { test, expect, TestDetails } from '../../playwright'; +import { asUser, withPlugins, withFeatureFlags } from '../../wordpress'; + +const details: TestDetails = { + annotation: [ + asUser( 'admin' ), + withPlugins( 'proxy-auth.php' ), + withFeatureFlags( 'proactiveUserEngagement' ), + ], +}; + +test.describe( 'email reporting', details, () => { + test( 'should add menu item to manage subscriptions', async ( { wp } ) => { + await wp.visitDashboard(); + + const userMenu = wp.page.getByRole( 'button', { name: 'Account' } ); + await expect( userMenu ).toBeVisible(); + await userMenu.click(); + + const menuItem = wp.page.getByRole( 'menuitem', { + name: 'Manage email reports', + } ); + await expect( menuItem ).toBeVisible(); + } ); +} ); diff --git a/tests/playwright/wordpress/wordpress.ts b/tests/playwright/wordpress/wordpress.ts index e4cb6eadfac..f08e0bb6954 100644 --- a/tests/playwright/wordpress/wordpress.ts +++ b/tests/playwright/wordpress/wordpress.ts @@ -241,6 +241,27 @@ export class WordPress { return this.page.goto( `${ this.baseURL }${ path }` ); } + /** + * Navigates to the Site Kit dashboard. + * + * @since n.e.x.t + * + * @param hash The hash to navigate to. + * @return {Promise} A promise that resolves when the page is navigated to. + */ + visitDashboard( hash = '' ): Promise< Response | null > { + let stepName = 'Visit Dashboard'; + if ( hash ) { + stepName += ` (#${ hash })`; + } + + return test.step( stepName, () => + this.visitAdmin( + `admin.php?page=googlesitekit-dashboard#${ hash }` + ) + ); + } + /** * Navigates to the given path in the admin area. * From fc2f995a47fb16bff2af3a06b835d10b7caf7e80 Mon Sep 17 00:00:00 2001 From: Eugene Manuilov Date: Mon, 16 Mar 2026 15:11:37 +0200 Subject: [PATCH 04/31] Update tests for email-reporting settings. --- .../SelectionPanel/SelectionPanelNotice.js | 1 + .../email-reporting/email-reporting.spec.ts | 52 ++++++++++++++++--- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/assets/js/components/SelectionPanel/SelectionPanelNotice.js b/assets/js/components/SelectionPanel/SelectionPanelNotice.js index b637201fb7d..5051431f69f 100644 --- a/assets/js/components/SelectionPanel/SelectionPanelNotice.js +++ b/assets/js/components/SelectionPanel/SelectionPanelNotice.js @@ -30,6 +30,7 @@ const SelectionPanelNotice = forwardRef( ( props, ref ) => { return ( diff --git a/tests/playwright/specs/email-reporting/email-reporting.spec.ts b/tests/playwright/specs/email-reporting/email-reporting.spec.ts index f0a63a19f86..6b7440c788a 100644 --- a/tests/playwright/specs/email-reporting/email-reporting.spec.ts +++ b/tests/playwright/specs/email-reporting/email-reporting.spec.ts @@ -31,16 +31,54 @@ const details: TestDetails = { }; test.describe( 'email reporting', details, () => { - test( 'should add menu item to manage subscriptions', async ( { wp } ) => { + test( 'should let user to select a subscription', async ( { wp } ) => { + // Go to the Site Kit dashboard page. await wp.visitDashboard(); - const userMenu = wp.page.getByRole( 'button', { name: 'Account' } ); - await expect( userMenu ).toBeVisible(); - await userMenu.click(); + // Open the email reporting settings panel. + await test.step( 'Open settings page', async () => { + const itemName = 'Manage email reports'; + await wp.page.getByRole( 'button', { name: 'Account' } ).click(); + await wp.page.getByRole( 'menuitem', { name: itemName } ).click(); + } ); + + const frequency = wp.page.getByRole( 'heading', { name: 'Frequency' } ); + const weekly = wp.page.getByRole( 'radio', { name: 'Weekly' } ); + const monthly = wp.page.getByRole( 'radio', { name: 'Monthly' } ); + const quarterly = wp.page.getByRole( 'radio', { name: 'Quarterly' } ); + + // Verify the settings panel state. + await test.step( 'Verify settings panel state', async () => { + // The frequency heading is visible. + await expect( frequency ).toBeVisible(); + + // The current subscription badge is not visible. + const currentSubscription = wp.page.locator( + '.googlesitekit-frequency-selector__current-subscription' + ); + await expect( currentSubscription ).not.toBeVisible(); + + // Frequency options are visible. + await expect( weekly ).toBeVisible(); + await expect( monthly ).toBeVisible(); + await expect( quarterly ).toBeVisible(); + + // Weekly option is checked by default. + await expect( weekly ).toBeChecked(); + } ); + + // Verify the monthly option can be selected. + await test.step( 'Set monthly option', async () => { + await monthly.click(); + await expect( weekly ).not.toBeChecked(); + await expect( monthly ).toBeChecked(); + + await wp.page.getByRole( 'button', { name: 'Subscribe' } ).click(); - const menuItem = wp.page.getByRole( 'menuitem', { - name: 'Manage email reports', + const notice = wp.page.getByRole( 'alert', { + name: 'successfully subscribed', + } ); + await expect( notice ).toBeVisible(); } ); - await expect( menuItem ).toBeVisible(); } ); } ); From dd644fabcc4f6052cf2b54485c66b045b422701c Mon Sep 17 00:00:00 2001 From: Eugene Manuilov Date: Mon, 16 Mar 2026 16:11:43 +0200 Subject: [PATCH 05/31] Update the email reporting test to verify subscription selection process. --- assets/js/components/Notice/index.js | 1 + .../SelectionPanel/SelectionPanelNotice.js | 1 - .../UserSettingsSelectionPanel/index.js | 2 +- .../email-reporting/email-reporting.spec.ts | 114 ++++++++++-------- 4 files changed, 66 insertions(+), 52 deletions(-) diff --git a/assets/js/components/Notice/index.js b/assets/js/components/Notice/index.js index c8e0188ebdc..a475350c2e7 100644 --- a/assets/js/components/Notice/index.js +++ b/assets/js/components/Notice/index.js @@ -57,6 +57,7 @@ const Notice = forwardRef( `googlesitekit-notice--${ type }`, className ) } + role="alert" > { ! hideIcon && (
diff --git a/assets/js/components/SelectionPanel/SelectionPanelNotice.js b/assets/js/components/SelectionPanel/SelectionPanelNotice.js index 5051431f69f..b637201fb7d 100644 --- a/assets/js/components/SelectionPanel/SelectionPanelNotice.js +++ b/assets/js/components/SelectionPanel/SelectionPanelNotice.js @@ -30,7 +30,6 @@ const SelectionPanelNotice = forwardRef( ( props, ref ) => { return ( diff --git a/assets/js/components/email-reporting/UserSettingsSelectionPanel/index.js b/assets/js/components/email-reporting/UserSettingsSelectionPanel/index.js index 13fdcdac7ca..3de2cd4344e 100644 --- a/assets/js/components/email-reporting/UserSettingsSelectionPanel/index.js +++ b/assets/js/components/email-reporting/UserSettingsSelectionPanel/index.js @@ -188,7 +188,7 @@ export default function UserSettingsSelectionPanel() { return ( { - test( 'should let user to select a subscription', async ( { wp } ) => { - // Go to the Site Kit dashboard page. - await wp.visitDashboard(); - - // Open the email reporting settings panel. - await test.step( 'Open settings page', async () => { - const itemName = 'Manage email reports'; - await wp.page.getByRole( 'button', { name: 'Account' } ).click(); - await wp.page.getByRole( 'menuitem', { name: itemName } ).click(); - } ); - - const frequency = wp.page.getByRole( 'heading', { name: 'Frequency' } ); - const weekly = wp.page.getByRole( 'radio', { name: 'Weekly' } ); - const monthly = wp.page.getByRole( 'radio', { name: 'Monthly' } ); - const quarterly = wp.page.getByRole( 'radio', { name: 'Quarterly' } ); - - // Verify the settings panel state. - await test.step( 'Verify settings panel state', async () => { - // The frequency heading is visible. - await expect( frequency ).toBeVisible(); - - // The current subscription badge is not visible. - const currentSubscription = wp.page.locator( - '.googlesitekit-frequency-selector__current-subscription' - ); - await expect( currentSubscription ).not.toBeVisible(); - - // Frequency options are visible. - await expect( weekly ).toBeVisible(); - await expect( monthly ).toBeVisible(); - await expect( quarterly ).toBeVisible(); - - // Weekly option is checked by default. - await expect( weekly ).toBeChecked(); - } ); - - // Verify the monthly option can be selected. - await test.step( 'Set monthly option', async () => { - await monthly.click(); - await expect( weekly ).not.toBeChecked(); - await expect( monthly ).toBeChecked(); - - await wp.page.getByRole( 'button', { name: 'Subscribe' } ).click(); - - const notice = wp.page.getByRole( 'alert', { - name: 'successfully subscribed', - } ); - await expect( notice ).toBeVisible(); - } ); +test( 'should let user select a subscription', details, async ( { wp } ) => { + // Go to the Site Kit dashboard page. + await wp.visitDashboard(); + + // Open the email reporting settings panel. + await test.step( 'Open settings page', async () => { + const itemName = 'Manage email reports'; + await wp.page.getByRole( 'button', { name: 'Account' } ).click(); + await wp.page.getByRole( 'menuitem', { name: itemName } ).click(); + } ); + + // Get the root element of the Email Reporting settings panel. + const root = wp.page.locator( '.googlesitekit-email-reporting-settings' ); + + const frequency = root.getByRole( 'heading', { name: 'Frequency' } ); + const currentSubscription = root.locator( + '.googlesitekit-frequency-selector__current-subscription' + ); + + const weekly = root.getByRole( 'radio', { name: 'Weekly' } ); + const monthly = root.getByRole( 'radio', { name: 'Monthly' } ); + const quarterly = root.getByRole( 'radio', { name: 'Quarterly' } ); + + // Verify the settings panel state. + await test.step( 'Verify settings panel state', async () => { + // The frequency heading is visible. + await expect( frequency ).toBeVisible(); + + // The current subscription badge is not visible. + await expect( currentSubscription ).not.toBeVisible(); + + // Frequency options are visible. + await expect( weekly ).toBeVisible(); + await expect( monthly ).toBeVisible(); + await expect( quarterly ).toBeVisible(); + + // Weekly option is checked by default. + await expect( weekly ).toBeChecked(); + } ); + + // Verify the monthly option can be selected. + await test.step( 'Set monthly option', async () => { + await monthly.click(); + await expect( weekly ).not.toBeChecked(); + await expect( monthly ).toBeChecked(); + + await root.getByRole( 'button', { name: 'Subscribe' } ).click(); + + const notice = root + .getByRole( 'alert' ) + .filter( { hasText: 'successfully subscribed' } ); + await expect( notice ).toBeVisible( { timeout: 10_000 } ); + } ); + + // Verify the settings panel state changed. + await test.step( 'Verify settings state changed', async () => { + const unsubscribe = root.getByRole( 'button', { name: 'Unsubscribe' } ); + const update = root.getByRole( 'button', { name: 'Update settings' } ); + + await expect( currentSubscription ).toBeVisible(); + await expect( weekly ).not.toBeChecked(); + await expect( monthly ).toBeChecked(); + await expect( unsubscribe ).toBeVisible(); + await expect( update ).toBeVisible(); } ); } ); From 5ddbe53eaee74132ba89906edd55ef636ac9641f Mon Sep 17 00:00:00 2001 From: Eugene Manuilov Date: Tue, 17 Mar 2026 10:13:35 +0200 Subject: [PATCH 06/31] De-duplicate PHP errors reported during tests to avoid redundant output. --- tests/playwright/wordpress/wordpress.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/playwright/wordpress/wordpress.ts b/tests/playwright/wordpress/wordpress.ts index f08e0bb6954..9bff27df2df 100644 --- a/tests/playwright/wordpress/wordpress.ts +++ b/tests/playwright/wordpress/wordpress.ts @@ -159,15 +159,16 @@ export class WordPress { } if ( errors.length > 0 ) { - const summary = errors - .map( - ( e ) => - `[${ e.level }] ${ e.message } (${ e.file }:${ e.line })` - ) - .join( '\n' ); + const uniqueErrors: string[] = []; + errors.forEach( ( e ) => { + uniqueErrors.push( + `[${ e.level }] ${ e.message } (${ e.file }:${ e.line })` + ); + } ); + const summary = uniqueErrors.join( '\n' ); throw new Error( - `${ errors.length } PHP error(s) during test:\n${ summary }` + `${ uniqueErrors.length } PHP error(s) during test:\n${ summary }` ); } } From 810e8b49f91d3e330fe45af0ce6985386d673d0a Mon Sep 17 00:00:00 2001 From: Eugene Manuilov Date: Tue, 17 Mar 2026 10:23:27 +0200 Subject: [PATCH 07/31] Update snapshots. --- .../__snapshots__/EmailReportingErrorNotices.test.js.snap | 2 ++ .../__snapshots__/GoogleTagGatewayToggle.test.js.snap | 1 + .../__snapshots__/KeyMetricsSetupApp.test.js.snap | 5 +++++ ...ogleTagGatewaySetupSuccessSubtleNotification.test.js.snap | 1 + .../__snapshots__/WPDashboardReportError.test.js.snap | 2 ++ .../EnhancedConversionsNotification.test.js.snap | 1 + .../EnhancedConversionsSettingsNotice.test.js.snap | 1 + .../AdBlockingRecoverySetupSuccessNotification.test.js.snap | 1 + .../__snapshots__/AudienceCreationNotice.test.js.snap | 1 + .../__snapshots__/index.test.js.snap | 1 + .../EnhancedConversionsNotification.test.js.snap | 1 + .../EnhancedConversionsSettingsNotice.test.js.snap | 1 + .../PolicyViolationSettingsNotice.test.tsx.snap | 2 ++ .../__snapshots__/index.test.tsx.snap | 2 ++ .../ProductIDContributionsNotification.test.js.snap | 1 + .../ProductIDSubscriptionsNotification.test.js.snap | 1 + .../components/setup/__snapshots__/SetupForm.test.js.snap | 1 + 17 files changed, 25 insertions(+) diff --git a/assets/js/components/email-reporting/notices/__snapshots__/EmailReportingErrorNotices.test.js.snap b/assets/js/components/email-reporting/notices/__snapshots__/EmailReportingErrorNotices.test.js.snap index 650f44921de..6a47e227d07 100644 --- a/assets/js/components/email-reporting/notices/__snapshots__/EmailReportingErrorNotices.test.js.snap +++ b/assets/js/components/email-reporting/notices/__snapshots__/EmailReportingErrorNotices.test.js.snap @@ -7,6 +7,7 @@ exports[`EmailReportingErrorNotices should render the permissions error notice w >