Skip to content

Commit

Permalink
Merge pull request #2477 from woocommerce/dev/e2e-notifications-schedule
Browse files Browse the repository at this point in the history
[API PULL] E2E TESTS for Notifications schedule
  • Loading branch information
puntope authored Jul 25, 2024
2 parents 153dd22 + bd200ed commit 2b78877
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ import { expect, test } from '@playwright/test';
/**
* Internal dependencies
*/
import SettingsPage from '../utils/pages/settings';
import { clearOnboardedMerchant, setOnboardedMerchant } from '../utils/api';
import { LOAD_STATE } from '../utils/constants';
import SettingsPage from '../../utils/pages/settings';
import { clearOnboardedMerchant, setOnboardedMerchant } from '../../utils/api';
import { LOAD_STATE } from '../../utils/constants';

test.use( { storageState: process.env.ADMINSTATE } );

test.describe.configure( { mode: 'serial' } );

/**
* @type {import('../utils/pages/settings.js').default } settingsPage
* @type {import('../../utils/pages/settings.js').default } settingsPage
*/
let settingsPage = null;

Expand All @@ -24,7 +24,7 @@ let settingsPage = null;
*/
let page = null;

test.describe( 'Notifications Feature', () => {
test.describe( 'Notifications Banner', () => {
test.beforeAll( async ( { browser } ) => {
page = await browser.newPage();
settingsPage = new SettingsPage( page );
Expand Down Expand Up @@ -52,7 +52,7 @@ test.describe( 'Notifications Feature', () => {

test( 'Grant Access button is visible on Settings page when notifications service is enabled', async () => {
// Mock Merchant Center as connected
settingsPage.mockMCConnected( 1234, true );
await settingsPage.mockMCConnected( 1234, true );
const button = settingsPage.getGrantAccessBtn();

await expect( button ).toBeVisible();
Expand All @@ -61,8 +61,8 @@ test.describe( 'Notifications Feature', () => {
test( 'When click on Grant Access button redirect to Auth page', async () => {
const mockAuthURL = 'https://example.com';
// Mock Merchant Center as connected
settingsPage.mockMCConnected( 1234, true );
settingsPage.fulfillRESTApiAuthorize( { auth_url: mockAuthURL } );
await settingsPage.mockMCConnected( 1234, true );
await settingsPage.fulfillRESTApiAuthorize( { auth_url: mockAuthURL } );
const button = settingsPage.getGrantAccessBtn();

button.click();
Expand All @@ -72,8 +72,8 @@ test.describe( 'Notifications Feature', () => {
} );

test( 'When REST API is Approved it shows a success notice in MC and allows to disable it', async () => {
settingsPage.goto();
settingsPage.mockMCConnected( 1234, true, 'approved' );
await settingsPage.goto();
await settingsPage.mockMCConnected( 1234, true, 'approved' );
const grantedAccessMessage = page
.locator( '#woocommerce-layout__primary' )
.getByText(
Expand All @@ -99,24 +99,24 @@ test.describe( 'Notifications Feature', () => {
} );

await expect( disableDataFetchButton ).toBeVisible();
disableDataFetchButton.click();
await disableDataFetchButton.click();

await expect( modalConfirmBtn ).toBeDisabled();
await expect( modalDismissBtn ).toBeEnabled();
await expect( modalCheck ).toBeVisible();
modalCheck.check();
await modalCheck.check();
await expect( modalConfirmBtn ).toBeEnabled();
modalConfirmBtn.click();
await modalConfirmBtn.click();
await page.waitForLoadState( LOAD_STATE.DOM_CONTENT_LOADED );
await page.waitForURL( /path=%2Fgoogle%2Fsettings/ );
await expect( modalConfirmBtn ).not.toBeVisible();
} );

test( 'When REST API is Error it shows a waring notice in MC and allows to grant access', async () => {
settingsPage.goto();
settingsPage.mockMCConnected( 1234, true, 'error' );
await settingsPage.goto();
await settingsPage.mockMCConnected( 1234, true, 'error' );
const mockAuthURL = 'https://example.com';
settingsPage.fulfillRESTApiAuthorize( { auth_url: mockAuthURL } );
await settingsPage.fulfillRESTApiAuthorize( { auth_url: mockAuthURL } );
const errorAccessMessage = page
.locator( '#woocommerce-layout__primary' )
.getByText(
Expand All @@ -128,7 +128,7 @@ test.describe( 'Notifications Feature', () => {
} );
await expect( errorAccessMessage ).toBeVisible();
await expect( grantAccessBtn ).toBeVisible();
grantAccessBtn.click();
await grantAccessBtn.click();
await page.waitForLoadState( LOAD_STATE.DOM_CONTENT_LOADED );
await page.waitForURL( mockAuthURL );
expect( page.url() ).toMatch( mockAuthURL );
Expand Down
117 changes: 117 additions & 0 deletions tests/e2e/specs/notifications/notifications-schedule.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/**
* External dependencies
*/
import { expect, test } from '@playwright/test';

/**
* Internal dependencies
*/
import { getClassicProductEditorUtils } from '../../utils/product-editor';
import MockRequests from '../../utils/mock-requests';
import {
setNotificationsReady,
clearOnboardedMerchant,
setOnboardedMerchant,
clearNotificationsReady,
} from '../../utils/api';

test.use( { storageState: process.env.ADMINSTATE } );

test.describe.configure( { mode: 'serial' } );

/**
* @type {import('../../utils/mock-requests.js').default } mockRequests
*/
let mockRequests = null;

/**
* @type {import('../../utils/product-editor.js').default } productEditor
*/
let productEditor = null;

/**
* @type {import('@playwright/test').Page} page
*/
let page = null;

const actionSchedulerLink =
'wp-admin/admin.php?page=wc-status&tab=action-scheduler&orderby=schedule&order=desc';

test.describe( 'Notifications Schedule', () => {
test.beforeAll( async ( { browser } ) => {
page = await browser.newPage();
productEditor = getClassicProductEditorUtils( page );
mockRequests = new MockRequests( page );
await setOnboardedMerchant();
await setNotificationsReady();
await Promise.all( [
// Mock Jetpack as connected
mockRequests.mockJetpackConnected(),

// Mock google as connected.
mockRequests.mockGoogleConnected(),
mockRequests.mockMCConnected( 1234, true, 'approved' ),
] );
} );

test.afterAll( async () => {
await clearOnboardedMerchant();
await clearNotificationsReady();
await page.close();
} );

test( 'When access is granted Notifications are scheduled', async () => {
// Create a new fresh product
await productEditor.gotoAddProductPage();
await productEditor.fillProductName();
await productEditor.publish();
const id = productEditor.getPostID();

// Check the product.create job is scheduled.
await page.goto( actionSchedulerLink );
let row = page.getByRole( 'row', {
name: `gla/jobs/notifications/products/process_item Run | Cancel Pending 0 => array ( 'item_id' => ${ id }, 'topic' => 'product.create'`,
} );
await expect( row ).toBeVisible();

// Hover the row, so the Run button gets visible
await row.hover( { force: true } );
await row.getByRole( 'link' ).first().click();

// Wait for the page to refresh and see that pending job is not there anymore.
await page.waitForURL( actionSchedulerLink );
await expect( row ).not.toBeVisible();

// edit the product and set it as notified
await productEditor.gotoEditProductPage( id );
await productEditor.mockNotificationStatus( 'created' );
await productEditor.fillProductName( 'updated product' );
await productEditor.save();

// Check if the product.update job is there.
await page.goto( actionSchedulerLink );
row = page.getByRole( 'row', {
name: `gla/jobs/notifications/products/process_item Run | Cancel Pending 0 => array ( 'item_id' => ${ id }, 'topic' => 'product.update'`,
} );
await expect( row ).toBeVisible();
await row.hover( { force: true } );
await row.getByRole( 'link' ).first().click();
await page.waitForURL( actionSchedulerLink );
await expect( row ).not.toBeVisible();

// change to external type. It will trigger the product.delete
await productEditor.gotoEditProductPage( id );
await productEditor.changeToExternalProduct();
await productEditor.save();
// Check if the product.delete job is there.
await page.goto( actionSchedulerLink );
row = page.getByRole( 'row', {
name: `gla/jobs/notifications/products/process_item Run | Cancel Pending 0 => array ( 'item_id' => ${ id }, 'topic' => 'product.delete'`,
} );
await expect( row ).toBeVisible();
await row.hover( { force: true } );
await row.getByRole( 'link' ).first().click();
await page.waitForURL( actionSchedulerLink );
await expect( row ).not.toBeVisible();
} );
} );
42 changes: 42 additions & 0 deletions tests/e2e/test-data/test-data.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
namespace Automattic\WooCommerce\GoogleListingsAndAds\TestData;

use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Options\TransientsInterface;

add_action( 'rest_api_init', __NAMESPACE__ . '\register_routes' );
add_filter( 'woocommerce_gla_notify', '__return_false'); // avoid any request to google in the tests

/**
* Register routes for setting test data.
Expand Down Expand Up @@ -49,6 +51,22 @@ function register_routes() {
],
],
);
register_rest_route(
'wc/v3',
'gla-test/notifications-ready',
[
[
'methods' => 'POST',
'callback' => __NAMESPACE__ . '\set_notifications_ready',
'permission_callback' => __NAMESPACE__ . '\permissions',
],
[
'methods' => 'DELETE',
'callback' => __NAMESPACE__ . '\clear_notifications_ready',
'permission_callback' => __NAMESPACE__ . '\permissions',
],
],
);
}

/**
Expand Down Expand Up @@ -113,3 +131,27 @@ function clear_conversion_id() {
function permissions() {
return current_user_can( 'manage_woocommerce' );
}

/**
* Set the Notifications Service as ready.
*/
function set_notifications_ready() {
/** @var OptionsInterface $options */
$options = woogle_get_container()->get( OptionsInterface::class );
$transients = woogle_get_container()->get( TransientsInterface::class );
$transients->set( TransientsInterface::URL_MATCHES, 'yes' );
$options->update(
OptionsInterface::WPCOM_REST_API_STATUS, 'approved'
);
}
/**
* Clear the Notifications Service.
*/
function clear_notifications_ready() {
/** @var OptionsInterface $options */
$options = woogle_get_container()->get( OptionsInterface::class );
$transients = woogle_get_container()->get( TransientsInterface::class );
$transients->delete( TransientsInterface::URL_MATCHES );
$options->delete( OptionsInterface::WPCOM_REST_API_STATUS );
}

14 changes: 14 additions & 0 deletions tests/e2e/utils/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,17 @@ export async function setOnboardedMerchant() {
export async function clearOnboardedMerchant() {
await api().delete( 'gla-test/onboarded-merchant' );
}

/**
* Set Notifications Ready.
*/
export async function setNotificationsReady() {
await api().post( 'gla-test/notifications-ready' );
}

/**
* Clear Onboarded Merchant.
*/
export async function clearNotificationsReady() {
await api().delete( 'gla-test/notifications-ready' );
}
44 changes: 44 additions & 0 deletions tests/e2e/utils/product-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ export function getClassicProductEditorUtils( page ) {
return page.locator( '.gla_attributes_multipack_field input' );
},

getPostID() {
const url = new URL( page.url() );
return url.searchParams.get( 'post' );
},

async getAvailableProductAttributesWithTestValues( locator = page ) {
return getAvailableProductAttributesWithTestValues(
locator,
Expand All @@ -165,6 +170,11 @@ export function getClassicProductEditorUtils( page ) {
await this.waitForInteractionReady();
},

async gotoEditProductPage( id ) {
await page.goto( `/wp-admin/post.php?post=${ id }&action=edit` );
await this.waitForInteractionReady();
},

async gotoEditVariableProductPage() {
const variableId = await api.createVariableWithVariationProducts();

Expand Down Expand Up @@ -210,6 +220,30 @@ export function getClassicProductEditorUtils( page ) {
await this.waitForInteractionReady();
},

clickPublish() {
return page
.getByRole( 'button', { name: 'Publish', exact: true } )
.click();
},

async publish() {
const observer = page.waitForResponse( ( response ) => {
const url = new URL( response.url() );

return (
url.pathname === '/wp-admin/post.php' &&
url.searchParams.has( 'post' ) &&
url.searchParams.has( 'action', 'edit' ) &&
response.ok() &&
response.request().method() === 'GET'
);
} );

await this.clickPublish();
await observer;
await this.waitForInteractionReady();
},

waitForInteractionReady() {
// Avoiding tests may start to operate the UI before jQuery interactions are initialized,
// leading to random failures.
Expand Down Expand Up @@ -273,6 +307,16 @@ export function getClassicProductEditorUtils( page ) {
],
} );
},
async mockNotificationStatus( status ) {
const url = new URL( page.url() );
const productId = url.searchParams.get( 'post' );

await api.api().put( `products/${ productId }`, {
meta_data: [
{ key: '_wc_gla_notification_status', value: status },
],
} );
},
};

return {
Expand Down

0 comments on commit 2b78877

Please sign in to comment.