Add basic e2e setup and tests for the Assembler Hub (#40235)

This commit is contained in:
Sam Seay 2023-09-20 11:59:06 +08:00 committed by GitHub
parent 10bb0cc822
commit cff7ee6ccc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 372 additions and 158 deletions

View File

@ -1,40 +1,38 @@
{
"phpVersion": "7.4",
"plugins": [
"."
],
"config": {
"JETPACK_AUTOLOAD_DEV": true,
"WP_DEBUG_LOG": true,
"WP_DEBUG_DISPLAY": true,
"ALTERNATE_WP_CRON": true
},
"mappings": {
"wp-cli.yml": "./tests/wp-cli.yml",
"wp-content/plugins/filter-setter.php": "./tests/e2e-pw/bin/filter-setter.php",
"wp-content/plugins/enable-experimental-features.php": "./tests/e2e-pw/bin/enable-experimental-features.php"
},
"lifecycleScripts": {
"afterStart": "./tests/e2e-pw/bin/test-env-setup.sh",
"afterClean": "./tests/e2e-pw/bin/test-env-setup.sh"
},
"env": {
"development": {},
"tests": {
"port": 8086,
"plugins": [
".",
"https://downloads.wordpress.org/plugin/akismet.zip",
"https://github.com/WP-API/Basic-Auth/archive/master.zip",
"https://downloads.wordpress.org/plugin/wp-mail-logging.zip"
],
"themes": [
"https://downloads.wordpress.org/theme/twentynineteen.zip"
],
"config": {
"WP_TESTS_DOMAIN": "localhost",
"ALTERNATE_WP_CRON": false
}
}
}
"phpVersion": "7.4",
"plugins": [ "." ],
"config": {
"JETPACK_AUTOLOAD_DEV": true,
"WP_DEBUG_LOG": true,
"WP_DEBUG_DISPLAY": true,
"ALTERNATE_WP_CRON": true
},
"mappings": {
"wp-cli.yml": "./tests/wp-cli.yml",
"wp-content/plugins/filter-setter.php": "./tests/e2e-pw/bin/filter-setter.php",
"wp-content/plugins/test-helper-apis.php": "./tests/e2e-pw/bin/test-helper-apis.php"
},
"lifecycleScripts": {
"afterStart": "./tests/e2e-pw/bin/test-env-setup.sh",
"afterClean": "./tests/e2e-pw/bin/test-env-setup.sh"
},
"env": {
"development": {},
"tests": {
"port": 8086,
"plugins": [
".",
"https://downloads.wordpress.org/plugin/akismet.zip",
"https://github.com/WP-API/Basic-Auth/archive/master.zip",
"https://downloads.wordpress.org/plugin/wp-mail-logging.zip"
],
"themes": [
"https://downloads.wordpress.org/theme/twentynineteen.zip"
],
"config": {
"WP_TESTS_DOMAIN": "localhost",
"ALTERNATE_WP_CRON": false
}
}
}
}

View File

@ -0,0 +1,4 @@
Significance: minor
Type: dev
Add some basic E2E tests for Assembler Hub

View File

@ -1,13 +0,0 @@
<?php
/**
* Plugin Name: Enable Experimental Features
* Description: Utility designed for E2E testing purposes. It activates experimental features in WooCommerce.
* @package Automattic\WooCommerce\E2EPlaywright
*/
function enable_experimental_features( $features ) {
$features['product-variation-management'] = true;
return $features;
}
add_filter( 'woocommerce_admin_get_feature_config', 'enable_experimental_features' );

View File

@ -12,8 +12,8 @@ wp-env run tests-cli wp rewrite structure '/%postname%/' --hard
echo -e 'Activate Filter Setter utility plugin \n'
wp-env run tests-cli wp plugin activate filter-setter
echo -e 'Activate Enable Experimental Features utility plugin \n'
wp-env run tests-cli wp plugin activate enable-experimental-features
echo -e 'Activate Test Helper APIs utility plugin \n'
wp-env run tests-cli wp plugin activate test-helper-apis
echo -e 'Add Customer user \n'
wp-env run tests-cli wp user create customer customer@woocommercecoree2etestsuite.com \

View File

@ -0,0 +1,106 @@
<?php
/**
* Plugin Name: Test Helper APIs
* Description: Utility REST API designed for E2E testing purposes. Allows turning features on or off, and setting option values
*/
function register_helper_api() {
register_rest_route(
'e2e-feature-flags',
'/update',
array(
'methods' => 'POST',
'callback' => 'update_feature_flags',
'permission_callback' => 'is_allowed',
)
);
register_rest_route(
'e2e-feature-flags',
'/reset',
array(
'methods' => 'GET',
'callback' => 'reset_feature_flags',
'permission_callback' => 'is_allowed',
)
);
register_rest_route(
'e2e-options',
'/update',
array(
'methods' => 'POST',
'callback' => 'api_update_option',
'permission_callback' => 'is_allowed',
)
);
}
add_action( 'rest_api_init', 'register_helper_api' );
/**
* Update feature flags
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response
*/
function update_feature_flags( WP_REST_Request $request ) {
$features = get_option( 'e2e_feature_flags', array() );
$new_features = json_decode( $request->get_body(), true );
if ( is_array( $new_features ) ) {
$features = array_merge( $features, $new_features );
update_option( 'e2e_feature_flags', $features );
return new WP_REST_Response( 'Feature flags updated', 200 );
}
return new WP_REST_Response( 'Invalid request body', 400 );
}
/**
* Reset feature flags
* @return WP_REST_Response
*/
function reset_feature_flags() {
delete_option( 'e2e_feature_flags' );
return new WP_REST_Response( 'Feature flags reset', 200 );
}
/**
* Enable experimental features
* @param array $features Array of features.
* @return array
*/
function enable_experimental_features( $features ) {
$stored_features = get_option( 'e2e_feature_flags', array() );
// We always enable this for tests at the moment.
$features['product-variation-management'] = true;
return array_merge( $features, $stored_features );
}
add_filter( 'woocommerce_admin_get_feature_config', 'enable_experimental_features' );
/**
* Update a WordPress option.
* @param WP_REST_Request $request
* @return WP_REST_Response
*/
function api_update_option( WP_REST_Request $request ) {
$option_name = sanitize_text_field( $request['option_name'] );
$option_value = sanitize_text_field( $request['option_value'] );
if ( update_option( $option_name, $option_value ) ) {
return new WP_REST_Response( 'Option updated', 200 );
}
return new WP_REST_Response( 'Invalid request body', 400 );
}
/**
* Check if user is admin
* @return bool
*/
function is_allowed() {
return current_user_can( 'manage_options' );
}

View File

@ -2,8 +2,6 @@ const { test, expect } = require( '@playwright/test' );
const { onboarding } = require( '../../utils' );
const { storeDetails } = require( '../../test-data/data' );
const { api } = require( '../../utils' );
const { features } = require( '../../utils' );
const { describe } = require('node:test');
test.skip( 'Store owner can complete onboarding wizard', () => {
test.use( { storageState: process.env.ADMINSTATE } );
@ -61,7 +59,6 @@ test.skip( 'Store owner can complete onboarding wizard', () => {
test( 'can discard industry changes when navigating back to "Store Details"', async ( {
page,
} ) => {
// set up pre-condition to ensure Industries stored in
// storeDetails.us.industries2 have been set
await onboarding.completeIndustrySection(
@ -80,7 +77,9 @@ test.skip( 'Store owner can complete onboarding wizard', () => {
if ( saveChangesModalVisible ) {
// Save the changes to ensure the test is now in the correct state
// independent of the previous test results
await onboarding.handleSaveChangesModal( page, { saveChanges: true } );
await onboarding.handleSaveChangesModal( page, {
saveChanges: true,
} );
}
// test proper begins
@ -156,101 +155,90 @@ test.skip( 'Store owner can complete onboarding wizard', () => {
} );
} );
// Skipping Onbaording tests as we're replacing StoreDetails with Core Profiler
// Skipping Onboarding tests as we're replacing StoreDetails with Core Profiler
// !Changed from Japanese to Liberian store, as Japanese Yen does not use decimals
test.skip(
'A Liberian store can complete the selective bundle install but does not include WCPay.',
() => {
test.use( { storageState: process.env.ADMINSTATE } );
test.skip( 'A Liberian store can complete the selective bundle install but does not include WCPay.', () => {
test.use( { storageState: process.env.ADMINSTATE } );
test.beforeEach( async () => {
// Complete "Store Details" step through the API to prevent flakiness when run on external sites.
await api.update.storeDetails( storeDetails.liberia.store );
} );
test.beforeEach( async () => {
// Complete "Store Details" step through the API to prevent flakiness when run on external sites.
await api.update.storeDetails( storeDetails.liberia.store );
} );
// eslint-disable-next-line jest/expect-expect
test( 'can choose the "Other" industry', async ( { page } ) => {
await onboarding.completeIndustrySection(
page,
storeDetails.liberia.industries,
storeDetails.liberia.expectedNumberOfIndustries
);
await page.locator( 'button >> text=Continue' ).click();
} );
// eslint-disable-next-line jest/expect-expect
test( 'can choose not to install any extensions', async ( {
// eslint-disable-next-line jest/expect-expect
test( 'can choose the "Other" industry', async ( { page } ) => {
await onboarding.completeIndustrySection(
page,
} ) => {
const expect_wp_pay = false;
storeDetails.liberia.industries,
storeDetails.liberia.expectedNumberOfIndustries
);
await page.locator( 'button >> text=Continue' ).click();
} );
await onboarding.completeIndustrySection(
page,
storeDetails.liberia.industries,
storeDetails.liberia.expectedNumberOfIndustries
);
await page.locator( 'button >> text=Continue' ).click();
// eslint-disable-next-line jest/expect-expect
test( 'can choose not to install any extensions', async ( { page } ) => {
const expect_wp_pay = false;
await onboarding.completeProductTypesSection(
page,
storeDetails.liberia.products
);
// Make sure WC Payments is NOT present
await expect(
page.locator(
'.woocommerce-admin__business-details__selective-extensions-bundle__description a[href*=woocommerce-payments]'
)
).not.toBeVisible();
await page.locator( 'button >> text=Continue' ).click();
await onboarding.completeBusinessDetailsSection( page );
await page.locator( 'button >> text=Continue' ).click();
await onboarding.unselectBusinessFeatures( page, expect_wp_pay );
await page.locator( 'button >> text=Continue' ).click();
await expect( page ).not.toHaveURL( /.*step=business-details/ );
} );
// Skipping this test because it's very flaky. Onboarding checklist changed so that the text
// changes when a task is completed.
// eslint-disable-next-line jest/no-disabled-tests
test.skip( 'should display the choose payments task, and not the WC Pay task', async ( {
await onboarding.completeIndustrySection(
page,
} ) => {
// If payment has previously been setup, the setup checklist will show something different
// This step resets it
await page.goto(
'wp-admin/admin.php?page=wc-settings&tab=checkout'
);
// Ensure that all payment methods are disabled
await expect(
page.locator( '.woocommerce-input-toggle--disabled' )
).toHaveCount( 3 );
// Checklist shows when completing setup wizard
await onboarding.completeBusinessDetailsSection( page );
await page.locator( 'button >> text=Continue' ).click();
storeDetails.liberia.industries,
storeDetails.liberia.expectedNumberOfIndustries
);
await page.locator( 'button >> text=Continue' ).click();
await onboarding.unselectBusinessFeatures( page, expect_wp_pay );
await page.locator( 'button >> text=Continue' ).click();
await onboarding.completeProductTypesSection(
page,
storeDetails.liberia.products
);
// Make sure WC Payments is NOT present
await expect(
page.locator(
'.woocommerce-admin__business-details__selective-extensions-bundle__description a[href*=woocommerce-payments]'
)
).not.toBeVisible();
// Start test
await page.waitForLoadState( 'networkidle' );
await expect(
page.locator(
':nth-match(.woocommerce-task-list__item-title, 3)'
)
).toContainText( 'Set up payments' );
await expect(
page.locator(
':nth-match(.woocommerce-task-list__item-title, 3)'
)
).not.toContainText( 'Set up WooPayments' );
} );
}
);
await page.locator( 'button >> text=Continue' ).click();
await onboarding.completeBusinessDetailsSection( page );
await page.locator( 'button >> text=Continue' ).click();
await onboarding.unselectBusinessFeatures( page, expect_wp_pay );
await page.locator( 'button >> text=Continue' ).click();
await expect( page ).not.toHaveURL( /.*step=business-details/ );
} );
// Skipping this test because it's very flaky. Onboarding checklist changed so that the text
// changes when a task is completed.
// eslint-disable-next-line jest/no-disabled-tests
test.skip( 'should display the choose payments task, and not the WC Pay task', async ( {
page,
} ) => {
// If payment has previously been setup, the setup checklist will show something different
// This step resets it
await page.goto( 'wp-admin/admin.php?page=wc-settings&tab=checkout' );
// Ensure that all payment methods are disabled
await expect(
page.locator( '.woocommerce-input-toggle--disabled' )
).toHaveCount( 3 );
// Checklist shows when completing setup wizard
await onboarding.completeBusinessDetailsSection( page );
await page.locator( 'button >> text=Continue' ).click();
await onboarding.unselectBusinessFeatures( page, expect_wp_pay );
await page.locator( 'button >> text=Continue' ).click();
// Start test
await page.waitForLoadState( 'networkidle' );
await expect(
page.locator( ':nth-match(.woocommerce-task-list__item-title, 3)' )
).toContainText( 'Set up payments' );
await expect(
page.locator( ':nth-match(.woocommerce-task-list__item-title, 3)' )
).not.toContainText( 'Set up WooPayments' );
} );
} );
// Skipping this test because it's very flaky.
test.skip( 'Store owner can go through setup Task List', () => {

View File

@ -1,6 +1,5 @@
const { test, expect } = require( '@playwright/test' );
const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default;
const { features } = require( '../../utils' );
test.describe( 'Payment setup task', () => {
test.use( { storageState: process.env.ADMINSTATE } );

View File

@ -0,0 +1,65 @@
const { test, expect, request } = require( '@playwright/test' );
const { features } = require( '../../utils' );
const { activateTheme } = require( '../../utils/themes' );
const { setOption } = require( '../../utils/options' );
const ASSEMBLER_HUB_URL =
'/wp-admin/admin.php?page=wc-admin&path=%2Fcustomize-store%2Fassembler-hub';
test.describe( 'Store owner can view Assembler Hub for store customization', () => {
test.use( { storageState: process.env.ADMINSTATE } );
test.beforeAll( async ( { baseURL } ) => {
// In some environments the tour blocks clicking other elements.
await setOption(
request,
baseURL,
'woocommerce_customize_store_onboarding_tour_hidden',
'yes'
);
await features.setFeatureFlag(
request,
baseURL,
'customize-store',
true
);
// Need a block enabled theme to test
await activateTheme( 'twentytwentythree' );
} );
test.afterAll( async ( { baseURL } ) => {
await features.resetFeatureFlags( request, baseURL );
// Reset theme back to twentynineteen
await activateTheme( 'twentynineteen' );
// Reset tour to visible.
await setOption(
request,
baseURL,
'woocommerce_customize_store_onboarding_tour_hidden',
'no'
);
} );
test( 'Can view the Assembler Hub page', async ( { page } ) => {
await page.goto( ASSEMBLER_HUB_URL );
const locator = page.locator( 'h1:visible' );
await expect( locator ).toHaveText( "Let's get creative" );
} );
test( 'Visiting change header should show a list of block patterns to choose from', async ( {
page,
} ) => {
await page.goto( ASSEMBLER_HUB_URL );
await page.click( 'text=Change your header' );
const locator = page.locator(
'.block-editor-block-patterns-list__list-item'
);
await expect( locator ).toHaveCount( 4 );
} );
} );

View File

@ -1,14 +1,41 @@
function is_enabled( feature ) {
const phase = process.env.WC_ADMIN_PHASE;
let config = 'development.json';
if ( ![ 'core','developer' ].includes( phase ) ) {
config = 'core.json';
}
const { encodeCredentials } = require( './plugin-utils' );
const features = require( `../../../client/admin/config/${config}` ).features;
return features[ feature ] && features[ feature ] === true;
}
const setFeatureFlag = async ( request, baseURL, flagName, enable ) => {
const apiContext = await request.newContext( {
baseURL,
extraHTTPHeaders: {
Authorization: `Basic ${ encodeCredentials(
'admin',
'password'
) }`,
cookie: '',
},
} );
await apiContext.post( '/wp-json/e2e-feature-flags/update', {
failOnStatusCode: true,
data: { [ flagName ]: enable },
} );
};
const resetFeatureFlags = async ( request, baseURL ) => {
const apiContext = await request.newContext( {
baseURL,
extraHTTPHeaders: {
Authorization: `Basic ${ encodeCredentials(
'admin',
'password'
) }`,
cookie: '',
},
} );
await apiContext.get( '/wp-json/e2e-feature-flags/reset', {
failOnStatusCode: true,
} );
};
module.exports = {
is_enabled
}
setFeatureFlag,
resetFeatureFlags,
};

View File

@ -0,0 +1,24 @@
import { encodeCredentials } from './plugin-utils';
export const setOption = async (
request,
baseURL,
optionName,
optionValue
) => {
const apiContext = await request.newContext( {
baseURL,
extraHTTPHeaders: {
Authorization: `Basic ${ encodeCredentials(
'admin',
'password'
) }`,
cookie: '',
},
} );
await apiContext.post( '/wp-json/e2e-options/update', {
failOnStatusCode: true,
data: { option_name: optionName, option_value: optionValue },
} );
};

View File

@ -12,7 +12,7 @@ const execAsync = promisify( require( 'child_process' ).exec );
* @param {string} password
* @returns Base64-encoded string
*/
const encodeCredentials = ( username, password ) => {
export const encodeCredentials = ( username, password ) => {
return Buffer.from( `${ username }:${ password }` ).toString( 'base64' );
};

View File

@ -0,0 +1,16 @@
const { exec } = require( 'node:child_process' );
export const activateTheme = ( themeName ) => {
return new Promise( ( resolve, reject ) => {
const command = `wp-env run tests-cli wp theme activate ${ themeName }`;
exec( command, ( error, stdout, stderr ) => {
if ( error ) {
console.error( `Error executing command: ${ error }` );
return reject( error );
}
resolve( stdout );
} );
} );
};

View File

@ -30,5 +30,5 @@ wp plugin activate filter-setter
# initialize pretty permalinks
wp rewrite structure /%postname%/
# Activate our Enable Experimental Features utility.
wp plugin activate enable-experimental-features
# Activate our helper APIs plugin.
wp plugin activate test-helper-apis