* Add initial E2E tests for purchase task

* Update paid theme logic to remove PHP warning and keep the correct price

* Fix php unit tests

* Address some PR feedback

* Add changelog

* Include the purchase task e2e test

* Disable test

* Delete purchase E2E test file
This commit is contained in:
louwie17 2022-03-18 16:05:38 -03:00 committed by GitHub
parent c05605fddf
commit 4bff4d1302
10 changed files with 239 additions and 39 deletions

View File

@ -0,0 +1,4 @@
Significance: patch
Type: Fix
Fix handling of paid themes in purchase task. #8493

View File

@ -92,12 +92,10 @@ class Theme extends Component {
location, location,
} ); } );
if ( slug !== activeTheme && getPriceValue( price ) <= 0 ) { if ( slug !== activeTheme && isInstalled ) {
if ( isInstalled ) { this.activateTheme( slug );
this.activateTheme( slug ); } else if ( slug !== activeTheme && getPriceValue( price ) <= 0 ) {
} else { this.installTheme( slug );
this.installTheme( slug );
}
} else { } else {
updateProfileItems( { theme: slug } ); updateProfileItems( { theme: slug } );
} }

View File

@ -9,12 +9,16 @@ import { Page } from 'puppeteer';
import { BusinessSection } from '../sections/onboarding/BusinessSection'; import { BusinessSection } from '../sections/onboarding/BusinessSection';
import { IndustrySection } from '../sections/onboarding/IndustrySection'; import { IndustrySection } from '../sections/onboarding/IndustrySection';
import { ProductTypeSection } from '../sections/onboarding/ProductTypesSection'; import { ProductTypeSection } from '../sections/onboarding/ProductTypesSection';
import { StoreDetailsSection } from '../sections/onboarding/StoreDetailsSection'; import {
StoreDetails,
StoreDetailsSection,
} from '../sections/onboarding/StoreDetailsSection';
import { ThemeSection } from '../sections/onboarding/ThemeSection'; import { ThemeSection } from '../sections/onboarding/ThemeSection';
import { BasePage } from './BasePage'; import { BasePage } from './BasePage';
/* eslint-disable @typescript-eslint/no-var-requires */ /* eslint-disable @typescript-eslint/no-var-requires */
const { expect } = require( '@jest/globals' ); const { expect } = require( '@jest/globals' );
const config = require( 'config' );
export class OnboardingWizard extends BasePage { export class OnboardingWizard extends BasePage {
url = 'wp-admin/admin.php?page=wc-admin&path=/setup-wizard'; url = 'wp-admin/admin.php?page=wc-admin&path=/setup-wizard';
@ -79,4 +83,75 @@ export class OnboardingWizard extends BasePage {
async goToOBWStep( step: string ): Promise< void > { async goToOBWStep( step: string ): Promise< void > {
await this.clickElementWithText( 'span', step ); await this.clickElementWithText( 'span', step );
} }
async walkThroughAndCompleteOnboardingWizard(
options: {
storeDetails?: StoreDetails;
industries?: string[];
products?: string[];
businessDetails?: {
productNumber: string;
currentlySelling: string;
};
themeTitle?: string;
} = {}
): Promise< void > {
await this.navigate();
await this.storeDetails.completeStoreDetailsSection(
options.storeDetails
);
// Wait for "Continue" button to become active
await this.continue();
// Wait for usage tracking pop-up window to appear
await this.optionallySelectUsageTracking();
// Query for the industries checkboxes
await this.industry.isDisplayed();
const industries = options.industries || [ 'Other' ];
for ( const industry of industries ) {
await this.industry.selectIndustry( industry );
}
await this.continue();
await this.productTypes.isDisplayed( 7 );
const products = options.products || [
'Physical products',
'Downloads',
];
for ( const product of products ) {
await this.productTypes.selectProduct( product );
}
await this.continue();
await page.waitForNavigation( {
waitUntil: 'networkidle0',
} );
await this.business.isDisplayed();
const businessDetails = options.businessDetails || {
productNumber: config.get( 'onboardingwizard.numberofproducts' ),
currentlySelling: config.get( 'onboardingwizard.sellingelsewhere' ),
};
await this.business.selectProductNumber(
businessDetails.productNumber
);
await this.business.selectCurrentlySelling(
businessDetails.currentlySelling
);
await this.continue();
await this.business.freeFeaturesIsDisplayed();
await this.business.expandRecommendedBusinessFeatures();
await this.business.uncheckAllRecommendedBusinessFeatures();
await this.continue();
await this.themes.isDisplayed();
// This navigates to the home screen
if ( options.themeTitle ) {
await this.themes.continueWithTheme( options.themeTitle );
} else {
await this.themes.continueWithActiveTheme();
}
}
} }

View File

@ -14,7 +14,7 @@ const {
const config = require( 'config' ); const config = require( 'config' );
/* eslint-enable @typescript-eslint/no-var-requires */ /* eslint-enable @typescript-eslint/no-var-requires */
interface StoreDetails { export interface StoreDetails {
addressLine1?: string; addressLine1?: string;
addressLine2?: string; addressLine2?: string;
countryRegionSubstring?: string; countryRegionSubstring?: string;

View File

@ -13,4 +13,17 @@ export class ThemeSection extends BasePage {
async continueWithActiveTheme(): Promise< void > { async continueWithActiveTheme(): Promise< void > {
await this.clickButtonWithText( 'Continue with my active theme' ); await this.clickButtonWithText( 'Continue with my active theme' );
} }
async continueWithTheme( themeTitle: string ): Promise< void > {
const title = await waitForElementByText( 'h2', themeTitle );
const card = await title?.evaluateHandle( ( element ) => {
return element.closest( '.components-card' );
} );
const chooseButton = await card
?.asElement()
?.$x( `//button[contains(text(), "Choose")]` );
if ( chooseButton && chooseButton.length > 0 ) {
await chooseButton[ 0 ].click();
}
}
} }

View File

@ -4,5 +4,6 @@ export * from './analytics/analytics';
export * from './analytics/analytics-overview'; export * from './analytics/analytics-overview';
export * from './marketing/coupons'; export * from './marketing/coupons';
export * from './tasks/payment'; export * from './tasks/payment';
export * from './tasks/purchase';
export * from './homescreen/task-list'; export * from './homescreen/task-list';
export * from './homescreen/activity-panel'; export * from './homescreen/activity-panel';

View File

@ -0,0 +1,100 @@
/**
* Internal dependencies
*/
import { resetWooCommerceState } from '../../fixtures';
import { Login } from '../../pages/Login';
import { OnboardingWizard } from '../../pages/OnboardingWizard';
import { WcHomescreen } from '../../pages/WcHomescreen';
import { getElementByText, waitForElementByText } from '../../utils/actions';
/* eslint-disable @typescript-eslint/no-var-requires */
const { afterAll, beforeAll, describe, it } = require( '@jest/globals' );
/* eslint-enable @typescript-eslint/no-var-requires */
const testAdminPurchaseSetupTask = () => {
describe( 'Purchase setup task', () => {
const profileWizard = new OnboardingWizard( page );
const homeScreen = new WcHomescreen( page );
const login = new Login( page );
beforeAll( async () => {
await login.login();
} );
afterAll( async () => {
await login.logout();
} );
describe( 'selecting paid product', () => {
beforeAll( async () => {
await resetWooCommerceState();
await profileWizard.navigate();
await profileWizard.walkThroughAndCompleteOnboardingWizard( {
products: [ 'Memberships' ],
} );
await homeScreen.isDisplayed();
await homeScreen.possiblyDismissWelcomeModal();
} );
it( 'should display add <product name> to my store task', async () => {
expect(
await getElementByText( '*', 'Add Memberships to my store' )
).toBeDefined();
} );
it( 'should show paid features modal with option to buy now', async () => {
const task = await getElementByText(
'*',
'Add Memberships to my store'
);
await task?.click();
await waitForElementByText(
'h1',
'Would you like to add the following paid features to your store now?'
);
expect(
await getElementByText( 'button', 'Buy now' )
).toBeDefined();
} );
} );
describe( 'selecting paid theme', () => {
beforeAll( async () => {
await resetWooCommerceState();
await profileWizard.navigate();
await profileWizard.walkThroughAndCompleteOnboardingWizard( {
themeTitle: 'Blooms',
} );
await homeScreen.isDisplayed();
await homeScreen.possiblyDismissWelcomeModal();
} );
it( 'should display add <theme name> to my store task', async () => {
expect(
await getElementByText( '*', 'Add Blooms to my store' )
).toBeDefined();
} );
it( 'should show paid features modal with option to buy now', async () => {
const task = await getElementByText(
'*',
'Add Blooms to my store'
);
await task?.click();
await waitForElementByText(
'h1',
'Would you like to add the following paid features to your store now?'
);
expect(
await getElementByText( 'button', 'Buy now' )
).toBeDefined();
} );
} );
} );
};
module.exports = { testAdminPurchaseSetupTask };

View File

@ -60,7 +60,7 @@ class OnboardingThemes {
$themes = self::get_themes(); $themes = self::get_themes();
$theme_key = array_search( $slug, array_column( $themes, 'slug' ), true ); $theme_key = array_search( $slug, array_column( $themes, 'slug' ), true );
$theme = false !== $theme_key ? $themes[ $theme_key ] : null; $theme = false !== $theme_key ? $themes[ $theme_key ] : null;
if ( $theme && isset( $theme['id'] ) && isset( $theme['price'] ) && ( ! isset( $theme['is_installed'] ) || ! $theme['is_installed'] ) ) { if ( $theme && isset( $theme['id'] ) && isset( $theme['price'] ) ) {
$price = self::get_price_from_string( $theme['price'] ); $price = self::get_price_from_string( $theme['price'] );
if ( $price && $price > 0 ) { if ( $price && $price > 0 ) {
return $themes[ $theme_key ]; return $themes[ $theme_key ];
@ -123,8 +123,13 @@ class OnboardingThemes {
$active_theme = get_option( 'stylesheet' ); $active_theme = get_option( 'stylesheet' );
foreach ( $installed_themes as $slug => $theme ) { foreach ( $installed_themes as $slug => $theme ) {
$theme_data = self::get_theme_data( $theme ); $theme_data = self::get_theme_data( $theme );
$themes[ $slug ] = $theme_data; if ( isset( $themes[ $slug ] ) ) {
$themes[ $slug ]['is_installed'] = true;
$themes[ $slug ]['image'] = $theme_data['image'];
} else {
$themes[ $slug ] = $theme_data;
}
} }
// Add the WooCommerce support tag for default themes that don't explicitly declare support. // Add the WooCommerce support tag for default themes that don't explicitly declare support.

View File

@ -6,7 +6,6 @@ use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProducts;
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingThemes; use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingThemes;
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile; use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile;
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task; use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
use Automattic\WooCommerce\Admin\PluginsHelper;
/** /**
* Purchase Task * Purchase Task
@ -55,7 +54,7 @@ class Purchase extends Task {
* @return string * @return string
*/ */
public function get_title() { public function get_title() {
$products = $this->get_products(); $products = $this->get_paid_products_and_themes();
return count( $products['remaining'] ) === 1 return count( $products['remaining'] ) === 1
? sprintf( ? sprintf(
@ -78,19 +77,20 @@ class Purchase extends Task {
* @return string * @return string
*/ */
public function get_content() { public function get_content() {
$products = $this->get_products(); $products = $this->get_paid_products_and_themes();
return count( $products['remaining'] ) === 1 if ( count( $products['remaining'] ) === 1 ) {
? $products['purchaseable'][0]['description'] return isset( $products['purchaseable'][0]['description'] ) ? $products['purchaseable'][0]['description'] : $products['purchaseable'][0]['excerpt'];
: sprintf( }
/* translators: %1$s: list of product names comma separated, %2%s the last product name */ return sprintf(
__( /* translators: %1$s: list of product names comma separated, %2%s the last product name */
'Good choice! You chose to add %1$s and %2$s to your store.', __(
'woocommerce-admin' 'Good choice! You chose to add %1$s and %2$s to your store.',
), 'woocommerce-admin'
implode( ', ', array_slice( $products['remaining'], 0, -1 ) ) . ( count( $products['remaining'] ) > 2 ? ',' : '' ), ),
end( $products['remaining'] ) implode( ', ', array_slice( $products['remaining'], 0, -1 ) ) . ( count( $products['remaining'] ) > 2 ? ',' : '' ),
); end( $products['remaining'] )
);
} }
/** /**
@ -118,7 +118,7 @@ class Purchase extends Task {
* @return bool * @return bool
*/ */
public function is_complete() { public function is_complete() {
$products = $this->get_products(); $products = $this->get_paid_products_and_themes();
return count( $products['remaining'] ) === 0; return count( $products['remaining'] ) === 0;
} }
@ -137,7 +137,7 @@ class Purchase extends Task {
* @return bool * @return bool
*/ */
public function can_view() { public function can_view() {
$products = $this->get_products(); $products = $this->get_paid_products_and_themes();
return count( $products['purchaseable'] ) > 0; return count( $products['purchaseable'] ) > 0;
} }
@ -146,18 +146,17 @@ class Purchase extends Task {
* *
* @return array purchaseable and remaining products and themes. * @return array purchaseable and remaining products and themes.
*/ */
public static function get_products() { public static function get_paid_products_and_themes() {
$relevant_products = OnboardingProducts::get_relevant_products(); $relevant_products = OnboardingProducts::get_relevant_products();
$profiler_data = get_option( OnboardingProfile::DATA_OPTION, array() ); $profiler_data = get_option( OnboardingProfile::DATA_OPTION, array() );
$theme = isset( $profiler_data['theme'] ) ? $profiler_data['theme'] : null; $theme = isset( $profiler_data['theme'] ) ? $profiler_data['theme'] : null;
$paid_theme = $theme ? OnboardingThemes::get_paid_theme_by_slug( $theme ) : null; $paid_theme = $theme ? OnboardingThemes::get_paid_theme_by_slug( $theme ) : null;
$installed = PluginsHelper::get_installed_plugin_slugs();
if ( $paid_theme ) { if ( $paid_theme ) {
$relevant_products['purchaseable'][] = $paid_theme; $relevant_products['purchaseable'][] = $paid_theme;
if ( ! in_array( $paid_theme['slug'], $installed, true ) ) { if ( isset( $paid_theme['is_installed'] ) && false === $paid_theme['is_installed'] ) {
$relevant_products['remaining'][] = $paid_theme['title']; $relevant_products['remaining'][] = $paid_theme['title'];
} }
} }

View File

@ -32,12 +32,16 @@ class WC_Tests_OnboardingTasks_Task_Purchase extends WC_Unit_Test_Case {
set_transient( set_transient(
OnboardingThemes::THEMES_TRANSIENT, OnboardingThemes::THEMES_TRANSIENT,
array( array(
'free' => array( 'slug' => 'free' ), 'free' => array(
'slug' => 'free',
'is_installed' => false,
),
'paid' => array( 'paid' => array(
'slug' => 'paid', 'slug' => 'paid',
'id' => 12312, 'id' => 12312,
'price' => '&#36;79.00', 'price' => '&#36;79.00',
'title' => 'theme title', 'title' => 'theme title',
'is_installed' => false,
), ),
'paid_installed' => array( 'paid_installed' => array(
'slug' => 'paid_installed', 'slug' => 'paid_installed',
@ -47,10 +51,11 @@ class WC_Tests_OnboardingTasks_Task_Purchase extends WC_Unit_Test_Case {
'is_installed' => true, 'is_installed' => true,
), ),
'free_with_price' => array( 'free_with_price' => array(
'slug' => 'free_with_price', 'slug' => 'free_with_price',
'id' => 12312, 'id' => 12312,
'price' => '&#36;0.00', 'price' => '&#36;0.00',
'title' => 'theme title', 'title' => 'theme title',
'is_installed' => false,
), ),
) )
); );