Merge branch 'trunk' into standardize-config-files

This commit is contained in:
Roy Ho 2021-12-16 05:44:48 -08:00 committed by GitHub
commit f7c93a6823
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1077 additions and 39 deletions

View File

@ -4,6 +4,7 @@
- A `specs/data` folder to store page element data.
- Tests to verify that different top-level menu and their associated sub-menus load successfully.
- Test scaffolding via `npx wc-e2e install @woocommerce/e2e-core-tests`
## Changed

View File

@ -20,6 +20,18 @@ Follow [E2E setup instructions](https://github.com/woocommerce/woocommerce/blob/
### Setting up core tests
#### Version 0.2.0 or newer
Version 0.2.0 added a test installer that will populate the `tests/e2e/specs` folder with test scripts for all the current core test suite. It also creates sample configuration files including all the configuration data needed to run the core tests.
- Install the e2e-environment `npm install @woocommerce/e2e-environment --save-dev`
- Run the installer `npx wc-e2e install @woocommerce/e2e-core-tests`
- Merge the sample configuration files:
- `tests/e2e/docker/woocommerce.e2e-core-tests.sh` => `initialize.sh`
- `tests/e2e/config/default-woocommerce.e2e-core-tests.json` => `default.json`
#### Version 0.1.X or other test runner
- Create the folder `tests/e2e/specs` in your repository if it does not exist.
- To add a core test to your test suite, create a new `.test.js` file within `tests/e2e/specs` . Example code to run all the shopper tests:
```js

View File

@ -0,0 +1,195 @@
{
"url": "http://localhost:8084/",
"users": {
"admin": {
"username": "admin",
"password": "password"
},
"customer": {
"username": "customer",
"password": "password"
}
},
"products": {
"simple": {
"name": "Simple product"
},
"variable": {
"name": "Variable Product with Three Attributes",
"defaultAttributes": [
{
"id": 0,
"name": "Size",
"option": "Medium"
},
{
"id": 0,
"name": "Colour",
"option": "Blue"
}
],
"attributes": [
{
"id": 0,
"name": "Colour",
"isVisibleOnProductPage": true,
"isForVariations": true,
"options": [
"Red",
"Green",
"Blue"
],
"sortOrder": 0
},
{
"id": 0,
"name": "Size",
"isVisibleOnProductPage": true,
"isForVariations": true,
"options": [
"Small",
"Medium",
"Large"
],
"sortOrder": 0
},
{
"id": 0,
"name": "Logo",
"isVisibleOnProductPage": true,
"isForVariations": true,
"options": [
"Woo",
"WordPress"
],
"sortOrder": 0
}
]
},
"variations": [
{
"regularPrice": "19.99",
"attributes": [
{
"name": "Size",
"option": "Large"
},
{
"name": "Colour",
"option": "Red"
}
]
},
{
"regularPrice": "18.99",
"attributes": [
{
"name": "Size",
"option": "Medium"
},
{
"name": "Colour",
"option": "Green"
}
]
},
{
"regularPrice": "17.99",
"attributes": [
{
"name": "Size",
"option": "Small"
},
{
"name": "Colour",
"option": "Blue"
}
]
}
],
"grouped": {
"name": "Grouped Product with Three Children",
"groupedProducts": [
{
"name": "Base Unit",
"regularPrice": "29.99"
},
{
"name": "Add-on A",
"regularPrice": "11.95"
},
{
"name": "Add-on B",
"regularPrice": "18.97"
}
]
},
"external": {
"name": "External product",
"regularPrice": "24.99",
"buttonText": "Buy now",
"externalUrl": "https://wordpress.org/plugins/woocommerce"
}
},
"coupons": {
"percentage": {
"code": "20percent",
"discountType": "percent",
"amount": "20.00"
}
},
"addresses": {
"admin": {
"store": {
"email": "admin@woocommercecoree2etestsuite.com",
"firstname": "John",
"lastname": "Doe",
"company": "Automattic",
"country": "United States (US)",
"addressfirstline": "addr 1",
"addresssecondline": "addr 2",
"countryandstate": "United States (US) — California",
"city": "San Francisco",
"state": "CA",
"postcode": "94107"
}
},
"customer": {
"billing": {
"firstname": "John",
"lastname": "Doe",
"company": "Automattic",
"country": "United States (US)",
"addressfirstline": "addr 1",
"addresssecondline": "addr 2",
"city": "San Francisco",
"state": "CA",
"postcode": "94107",
"phone": "123456789",
"email": "john.doe@example.com"
},
"shipping": {
"firstname": "John",
"lastname": "Doe",
"company": "Automattic",
"country": "United States (US)",
"addressfirstline": "addr 1",
"addresssecondline": "addr 2",
"city": "San Francisco",
"state": "CA",
"postcode": "94107"
}
}
},
"orders": {
"basicPaidOrder": {
"paymentMethod": "cod",
"status": "processing",
"billing": {
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@example.com"
}
}
}
}

View File

@ -0,0 +1,5 @@
module.exports = {
testSpecs: 'installFiles/scaffold-tests.json',
defaultJson: 'installFiles/default-test-config.json',
initializeSh: 'installFiles/initialize.sh.default',
};

View File

@ -0,0 +1,25 @@
#!/bin/bash
echo "Initializing WooCommerce E2E"
# This is a workaround to accommodate different directory names.
wp plugin activate --all
wp plugin deactivate akismet
wp plugin deactivate hello
wp theme install twentynineteen --activate
wp user create customer customer@woocommercecoree2etestsuite.com \
--user_pass=password \
--role=subscriber \
--first_name='Jane' \
--last_name='Smith' \
--path=/var/www/html
# we cannot create API keys for the API, so we using basic auth, this plugin allows that.
wp plugin install https://github.com/WP-API/Basic-Auth/archive/master.zip --activate
# install the WP Mail Logging plugin to test emails
wp plugin install wp-mail-logging --activate
# initialize pretty permalinks
wp rewrite structure /%postname%/

View File

@ -0,0 +1,138 @@
{
"active": [
{
"name": "front-end",
"description": "Shopper tests",
"testFiles": [
{
"name": "cart-begin",
"functions": [ "runCartPageTest" ]
}, {
"name": "cart-calculate-shipping",
"functions": [ "runCartCalculateShippingTest" ]
}, {
"name": "cart-coupons",
"functions": [ "runCartApplyCouponsTest" ]
}, {
"name": "checkout-begin",
"functions": [ "runCheckoutPageTest" ]
}, {
"name": "checkout-coupons",
"functions": [ "runCheckoutApplyCouponsTest" ]
}, {
"name": "checkout-create-account",
"functions": [ "runCheckoutCreateAccountTest" ]
}, {
"name": "checkout-login-account",
"functions": [ "runCheckoutLoginAccountTest" ]
}, {
"name": "my-account-create-account",
"functions": [ "runMyAccountCreateAccountTest" ]
}, {
"name": "my-account-pay-order",
"functions": [ "runMyAccountPayOrderTest" ]
}, {
"name": "my-account",
"functions": [ "runMyAccountPageTest" ]
}, {
"name": "order-email-receiving",
"functions": [ "runOrderEmailReceivingTest" ]
}, {
"name": "product-browse-search-sort",
"functions": [ "runProductBrowseSearchSortTest" ]
}, {
"name": "single-product-page",
"functions": [ "runSingleProductPageTest" ]
}, {
"name": "variable-product-updates",
"functions": [ "runVariableProductUpdateTest" ]
}
]
}, {
"name": "rest-api",
"description": "REST API tests",
"testFiles": [
{
"name": "api",
"functions": [ "runApiTests" ]
}
]
}, {
"name": "wp-admin",
"description": "Merchant tests",
"testFiles": [
{
"name": "create-coupon",
"functions": [ "runCreateCouponTest" ]
}, {
"name": "create-order",
"functions": [ "runCreateOrderTest" ]
}, {
"name": "create-shipping-classes",
"functions": [ "runAddShippingClassesTest" ]
}, {
"name": "create-shipping-zones",
"functions": [ "runAddNewShippingZoneTest" ]
}, {
"name": "create-simple-product",
"functions": [ "runAddSimpleProductTest" ]
}, {
"name": "create-variable-product",
"functions": [ "runAddVariableProductTest" ]
}, {
"name": "order-coupon",
"functions": [ "runOrderApplyCouponTest" ]
}, {
"name": "order-customer-payment-page",
"functions": [ "runMerchantOrdersCustomerPaymentPage" ]
}, {
"name": "order-edit",
"functions": [ "runEditOrderTest" ]
}, {
"name": "order-emails",
"functions": [ "runMerchantOrderEmailsTest" ]
}, {
"name": "order-refund",
"functions": [ "runOrderRefundTest" ]
}, {
"name": "order-searching",
"functions": [ "runOrderSearchingTest" ]
}, {
"name": "order-status-filters",
"functions": [ "runOrderStatusFiltersTest" ]
}, {
"name": "product-edit",
"functions": [ "runProductEditDetailsTest" ]
}, {
"name": "product-import-csv",
"functions": [ "runImportProductsTest" ]
}, {
"name": "product-search",
"functions": [ "runProductSearchTest" ]
}, {
"name": "update-general-settings",
"functions": [ "runUpdateGeneralSettingsTest" ]
}, {
"name": "update-product-settings",
"functions": [ "runProductSettingsTest" ]
}, {
"name": "update-tax-settings",
"functions": [ "runTaxSettingsTest" ]
}, {
"name": "wccom-connect",
"functions": [ "runInitiateWccomConnectionTest" ]
}
]
}
],
"deprecated": [
{
"name": "example-folder",
"testFiles": [
{ "name": "any-filename-to-deprecate" }
]
}
]
}

View File

@ -3,10 +3,12 @@
## Added
- Added `await` for every call to `shopper.logout`
- Test setup, scaffolding, and removal via `wc-e2e install` and `wc-e2e uninstall`
## Fixed
- Updated the browserViewport in `jest.setup.js` to match the `defaultViewport` dimensions defined in `jest-puppeteer.config.js`
## Added
- Added quotes around `WORDPRESS_TITLE` value in .env file to address issue with docker compose 2 "key cannot contain a space" error.

View File

@ -9,6 +9,19 @@ npm install @woocommerce/e2e-environment --save
npm install jest --global
```
### Version 0.3.0 and newer
Version 0.3.0 added a test installer that will populate the `tests/e2e/*` folder with test scripts and configuration files. The installer will create test scripts for E2E test packages that include support for the installer.
- [Adding test scaffolding to E2E test packages](https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/e2e-environment/test-packages.md)
#### Using the installer
- Install a default test environment: `npx wc-e2e install`
- Install test specs from an E2E tests package: `npx wc-e2e install @woocommerce-e2e-tests [--format cjs] [--ext spec.js]`
- The default test spec format and extension are `ES6` and `test.js`
- Remove test specs for an E2E tests package: `npx wc-e2e uninstall @woocommerce-e2e-tests`
## Configuration
The `@woocommerce/e2e-environment` package exports configuration objects that can be consumed in JavaScript config files in your project. Additionally, it includes a basic hosting container for running tests and includes instructions for creating your Travis CI setup.
@ -60,10 +73,10 @@ The E2E environment uses Jest as a test runner. Extending the base config is nec
```js
const path = require( 'path' );
const { useE2EJestConfig } = require( '@woocommerce/e2e-environment' );
const { useE2EJestConfig, resolveLocalE2ePath } = require( '@woocommerce/e2e-environment' );
const jestConfig = useE2EJestConfig( {
roots: [ path.resolve( __dirname, '../specs' ) ],
roots: [ resolveLocalE2ePath( 'specs' ) ],
} );
module.exports = jestConfig;

View File

@ -11,6 +11,7 @@ const {
getAppName,
getTestConfig,
resolveLocalE2ePath,
resolvePackagePath,
} = require( '../utils' );
const dockerArgs = [];
@ -63,7 +64,7 @@ if ( appPath ) {
if ( fs.existsSync( appInitFile ) ) {
fs.copyFileSync(
appInitFile,
path.resolve( __dirname, '../docker/wp-cli/initialize.sh' )
resolvePackagePath( 'docker/wp-cli/initialize.sh' )
);
console.log( 'Initializing ' + appInitFile );
}
@ -90,7 +91,7 @@ if ( ! process.env.WORDPRESS_URL ) {
}
// Ensure that the first Docker compose file loaded is from our local env.
dockerArgs.unshift( '-f', path.resolve( __dirname, '../docker-compose.yaml' ) );
dockerArgs.unshift( '-f', resolvePackagePath( 'docker-compose.yaml' ) );
const dockerProcess = spawnSync( 'docker-compose', dockerArgs, {
stdio: 'inherit',

View File

@ -4,7 +4,11 @@ const { spawnSync } = require( 'child_process' );
const program = require( 'commander' );
const path = require( 'path' );
const fs = require( 'fs' );
const { getAppRoot, resolveLocalE2ePath } = require( '../utils' );
const {
getAppRoot,
resolveLocalE2ePath,
resolvePackagePath,
} = require( '../utils' );
const {
WC_E2E_SCREENSHOTS,
JEST_PUPPETEER_CONFIG,
@ -30,7 +34,7 @@ if ( WC_E2E_SCREENSHOTS ) {
}
}
const nodeConfigDirs = [ path.resolve( __dirname, '../config' ) ];
const nodeConfigDirs = [ resolvePackagePath( 'config' ) ];
if ( appPath ) {
nodeConfigDirs.unshift( resolveLocalE2ePath( 'config' ) );
@ -51,10 +55,7 @@ if ( ! JEST_PUPPETEER_CONFIG ) {
// Use local Puppeteer config if there is one.
// Load test configuration file into an object.
const localJestConfigFile = resolveLocalE2ePath( 'config/jest-puppeteer.config.js' );
const jestConfigFile = path.resolve(
__dirname,
'../config/jest-puppeteer.config.js'
);
const jestConfigFile = resolvePackagePath( 'config/jest-puppeteer.config.js' );
testEnvVars.JEST_PUPPETEER_CONFIG = fs.existsSync( localJestConfigFile )
? localJestConfigFile
@ -88,7 +89,7 @@ if ( program.debug ) {
const envVars = Object.assign( {}, process.env, testEnvVars );
let configPath = path.resolve( __dirname, '../config/jest.config.js' );
let configPath = resolvePackagePath( 'config/jest.config.js' );
// Look for a Jest config in the dependent app's path.
if ( appPath ) {

View File

@ -0,0 +1,233 @@
#!/usr/bin/env node
/**
* External dependencies.
*/
const fs = require( 'fs' );
const path = require( 'path' );
const sprintf = require( 'sprintf-js' ).sprintf;
/**
* Internal dependencies.
*/
const {
resolvePackage,
resolvePackagePath,
} = require( '../utils' );
const {
createLocalE2ePath,
confirm,
confirmLocalCopy,
confirmLocalDelete,
getPackageData,
installDefaults
} = require( '../utils/scaffold' );
const args = process.argv.slice( 2 );
const [ command, packageName ] = args;
// Allow multiple spec file extensions and formats.
let testExtension = 'test.js';
let testFormat = '';
for ( let a = 2; a < args.length; a++ ) {
const nextArg = a + 1;
if ( nextArg >= args.length ) {
break;
}
switch ( args[ a ] ) {
case '--format':
testFormat = args[ nextArg ];
break;
case '--ext':
testExtension = args[ nextArg ];
break;
}
}
/**
* Install the test scripts and sample default.json configuration
*/
if ( command == 'install' ) {
// Install some environment defaults if no package is requested.
if ( ! packageName ) {
installDefaults();
return;
}
// `package` is a reserved word
const pkg = resolvePackage( packageName ).name;
if ( ! pkg.length ) {
//@todo add error message
return;
}
const { packageSlug, testSpecs, defaultJson, initializeSh } = getPackageData( pkg );
// Write sample default.json
if ( defaultJson ) {
const defaultJsonName = `config${path.sep}default-${packageSlug}.json`;
createLocalE2ePath( 'config' );
if ( confirmLocalCopy( defaultJsonName, defaultJson, pkg ) ) {
console.log( `Created sample test configuration to 'tests/e2e/${defaultJsonName}'.` );
}
}
// Write sample initialize.sh
if ( initializeSh ) {
const defaultInitName = `docker${path.sep}${packageSlug}.sh`;
createLocalE2ePath( 'docker' );
if ( confirmLocalCopy( defaultInitName, initializeSh, pkg ) ) {
console.log( `Created sample test container initialization script to 'tests/e2e/${defaultInitName}'.` );
}
}
if ( ! testSpecs ) {
return;
}
// Write test files
const testsSpecFile = resolvePackagePath( testSpecs, pkg );
const specs = fs.readFileSync( testsSpecFile );
const tests = JSON.parse( specs );
const { active, deprecated } = tests;
if ( active && active.length ) {
const blankLine = '';
const eol = "\n";
const autoGenerate = sprintf( '/* This file was auto-generated by the command `npx wc-e2e install %s`. */', packageName );
let importLineFormat;
let overwriteFiles;
let confirmPrompt;
if ( testFormat.toLowerCase() == 'cjs' ) {
importLineFormat = sprintf( "const {%%s} = require( '%s' );", pkg );
} else {
importLineFormat = sprintf( "import {%%s} from '%s';", pkg );
}
// Create the specs folder if not present
let specFolderPath = createLocalE2ePath( 'specs' );
// Loop through folders and files to write test scripts.
for ( let f = 0; f < active.length; f++ ) {
if ( overwriteFiles == 'q' ) {
overwriteFiles = '';
break;
}
const testFolder = active[ f ];
const { testFiles } = testFolder;
if ( ! testFiles || ! testFiles.length ) {
continue;
}
let specFolder;
if ( testFolder.name.length ) {
specFolder = createLocalE2ePath( `specs${path.sep}${testFolder.name}` );
} else {
specFolder = specFolderPath;
}
// Create the test files.
for ( let t = 0; t < testFiles.length; t++ ) {
const testFile = testFiles[ t ];
if ( ! testFile.functions.length ) {
continue;
}
const testFileName = `${testFolder.name}${path.sep}${testFile.name}.${testExtension}`;
const testFilePath = `${specFolder}${path.sep}${testFile.name}.${testExtension}`;
// Check to see if file exists.
if ( fs.existsSync( testFilePath ) ) {
if ( overwriteFiles != 'a' ) {
confirmPrompt = `${testFileName} already exists. Overwrite? [y]es/[n]o/[a]ll/[q]uit: `;
overwriteFiles = confirm( confirmPrompt, 'anqy' );
overwriteFiles = overwriteFiles.toLowerCase();
}
if ( overwriteFiles == 'q' ) {
break;
}
if ( overwriteFiles != 'a' && overwriteFiles != 'y' ) {
continue;
}
}
console.log( 'Writing tests/e2e/specs/' + testFileName );
let buffer = [ autoGenerate ];
let testSeparator, testTerminator, importPrefix;
// Add the import line.
if ( testFile.functions.length > 3 ) {
testSeparator = ',' + eol;
testTerminator = eol;
importPrefix = eol;
} else {
testSeparator = ', ';
testTerminator = ' ';
importPrefix = ' ';
}
const testImport = testFile.functions.join( testSeparator ) + testTerminator;
buffer.push( sprintf( importLineFormat, importPrefix + testImport ), blankLine );
// Add test function calls and write the file
let functionCalls = testFile.functions.map( functionName => functionName + '();' );
buffer.push( ...functionCalls, blankLine );
fs.writeFileSync( testFilePath, buffer.join( eol ) );
}
}
}
// @todo: deprecated files.
} else if ( command == 'uninstall' ) {
if ( ! packageName ) {
// @todo: write error message
return;
}
const pkg = resolvePackage( packageName ).name;
const { packageSlug, testSpecs, defaultJson, initializeSh } = getPackageData( pkg );
// Delete sample default.json
if ( defaultJson ) {
const defaultJsonName = `config${path.sep}default-${packageSlug}.json`;
confirmLocalDelete( defaultJsonName );
}
// Delete sample initialize.sh
if ( initializeSh ) {
const defaultInitName = `docker${path.sep}${packageSlug}.sh`;
confirmLocalDelete( defaultInitName );
}
if ( ! testSpecs ) {
return;
}
const testsSpecFile = resolvePackagePath( testSpecs, pkg );
const specs = fs.readFileSync( testsSpecFile );
const tests = JSON.parse( specs );
const { active } = tests;
if ( ! active || ! active.length ) {
return;
}
// Loop through folders and files to delete test scripts.
for ( let f = 0; f < active.length; f++ ) {
const testFolder = active[ f ];
const { testFiles } = testFolder;
if ( ! testFiles || ! testFiles.length ) {
continue;
}
const specFolder = testFolder.name.length ? `specs${path.sep}${testFolder.name}` : 'specs';
for ( let t = 0; t < testFiles.length; t++ ) {
const testFile = testFiles[ t ];
const testFilePath = `${specFolder}${path.sep}${testFile.name}.${testExtension}`;
confirmLocalDelete( testFilePath );
}
}
}

View File

@ -71,6 +71,10 @@ case $1 in
./bin/wait-for-build.sh && ./bin/e2e-test-integration.js --dev --debug $2
TESTRESULT=$?
;;
'install' | \
'uninstall')
./bin/scaffold.js $@
;;
*)
usage
;;

View File

@ -0,0 +1,25 @@
#!/bin/bash
echo "Initializing WooCommerce E2E"
# This is a workaround to accommodate different directory names.
wp plugin activate --all
wp plugin deactivate akismet
wp plugin deactivate hello
wp theme install twentynineteen --activate
wp user create customer customer@woocommercecoree2etestsuite.com \
--user_pass=password \
--role=subscriber \
--first_name='Jane' \
--last_name='Smith' \
--path=/var/www/html
# we cannot create API keys for the API, so we using basic auth, this plugin allows that.
wp plugin install https://github.com/WP-API/Basic-Auth/archive/master.zip --activate
# install the WP Mail Logging plugin to test emails
wp plugin install wp-mail-logging --activate
# initialize pretty permalinks
wp rewrite structure /%postname%/

View File

@ -0,0 +1,8 @@
const path = require( 'path' );
const { useE2EJestConfig, getAppRoot } = require( '@woocommerce/e2e-environment' );
const jestConfig = useE2EJestConfig( {
roots: [ path.resolve( __dirname, '../specs' ) ],
} );
module.exports = jestConfig;

View File

@ -0,0 +1,80 @@
import {
clearLocalStorage,
setBrowserViewport,
withRestApi,
WP_ADMIN_LOGIN
} from '@woocommerce/e2e-utils';
const config = require( 'config' );
const { HTTPClientFactory } = require( '@woocommerce/api' );
const { addConsoleSuppression, updateReadyPageStatus } = require( '@woocommerce/e2e-environment' );
const { DEFAULT_TIMEOUT_OVERRIDE } = process.env;
// @todo: remove this once https://github.com/woocommerce/woocommerce-admin/issues/6992 has been addressed
addConsoleSuppression( 'woocommerce_shared_settings', false );
/**
* Uses the WordPress API to delete all existing posts
*/
async function trashExistingPosts() {
const apiUrl = config.get('url');
const wpPostsEndpoint = '/wp/v2/posts';
const adminUsername = config.get('users.admin.username');
const adminPassword = config.get('users.admin.password');
const client = HTTPClientFactory.build(apiUrl)
.withBasicAuth(adminUsername, adminPassword)
.create();
// List all existing posts
const response = await client.get(wpPostsEndpoint);
const posts = response.data;
// Delete each post
for (const post of posts) {
await client.delete(`${wpPostsEndpoint}/${post.id}`);
}
}
// Before every test suite run, delete all content created by the test. This ensures
// other posts/comments/etc. aren't dirtying tests and tests don't depend on
// each other's side-effects.
beforeAll(async () => {
if ( DEFAULT_TIMEOUT_OVERRIDE ) {
page.setDefaultNavigationTimeout( DEFAULT_TIMEOUT_OVERRIDE );
page.setDefaultTimeout( DEFAULT_TIMEOUT_OVERRIDE );
}
try {
// Update the ready page to prevent concurrent test runs
await updateReadyPageStatus('draft');
await trashExistingPosts();
await withRestApi.deleteAllProducts();
await withRestApi.deleteAllCoupons();
await withRestApi.deleteAllOrders();
} catch ( error ) {
// Prevent an error here causing tests to fail.
}
await page.goto(WP_ADMIN_LOGIN);
await clearLocalStorage();
await setBrowserViewport( {
width: 1280,
height: 800,
});
});
// Clear browser cookies and cache using DevTools.
// This is to ensure that each test ends with no user logged in.
afterAll(async () => {
// Reset the ready page to published to allow future test runs
try {
await updateReadyPageStatus('publish');
} catch ( error ) {
// Prevent an error here causing tests to fail.
}
const client = await page.target().createCDPSession();
await client.send('Network.clearBrowserCookies');
await client.send('Network.clearBrowserCache');
});

View File

@ -33,7 +33,9 @@
"jest-each": "25.5.0",
"jest-puppeteer": "^4.4.0",
"node-stream-zip": "^1.13.6",
"request": "^2.88.2"
"readline-sync": "^1.4.10",
"request": "^2.88.2",
"sprintf-js": "^1.1.2"
},
"devDependencies": {
"@babel/cli": "7.12.8",

View File

@ -0,0 +1,104 @@
# WooCommerce End-to-End Test Packages
There are two limitations which significantly impact the architecture of E2E test packages:
- Referencing the `jest` functions `describe`, `it`, `beforeAll`, etc. throws a fatal error outside the `jest` environment.
- `jest` will not scan for tests in any path containing `node_mdules`.
## Creating a tests package
The way to create a tests package with the above limitations is
- **In the tests package**, wrap each test in a function
```js
/**
* Require the necessary jest functions to prevent the package build from referencing them
* `import` references imported functions during package build
*/
const { describe, it, beforeAll } = require( '@jest/globals' );
const testMyCriticalFlow = () => {
describe( 'My Critical Flow', () => {
beforeAll( async () => {
// Test setup
} );
it( 'can complete first step', async () => {
// Do stuff
expect( someValue ).toBeTruthy();
} );
} );
};
modules.exports = testMyFlow;
```
- **In the `tests/e2e/specs` folder**, create a test spec that calls the test function
```js
import { testMyCriticalFlow } from 'MyTestsPackage';
testMyCriticalFlow();
```
## Adding the scaffolds for the test installer
To work with the limitations outlined above, the test installer needs to access the test scaffolding information without accessing the package index. As a result, the `installFiles` is a required path in the steps below
- Create an `installFiles` folder in the root of the package
- Add an `index.js` to the folder which exports an object with some or all of three properties
```js
module.exports = {
defaultJson: 'installFiles/default-test-config.json',
initializeSh: 'installFiles/initialize.sh.default',
testSpecs: 'installFiles/scaffold-tests.json',
};
```
- The value of each of the properties should be a relative path from the package `index.js`. The test installer will remove `dist`, `build`, and `build-modules` from the end of the package index path.
- `defaultJson`: Path to a JSON file containing all `default.json` entries needed for the tests in the package.
- `initializeSh`: Path to a bash script containing the WP CLI commands needed to initialize the `e2e-environment` test container.
- `testSpecs`: Path to a JSON file containing a nested object
```json
{
"active": [
{
"name": "first-folder-name",
"description": "First tests",
"testFiles": [
{
"name": "test-name-a",
"functions": [
"testMyCriticalFlow"
]
},
{
"name": "test-name-b",
"functions": [
"testSecondCriticalFlow",
"testThirdCriticalFlow"
]
}
]
},
{
"name": "second-folder-name",
"description": "Second tests",
"testFiles": [
....
]
}
]
}
```
The test installer uses the `testSpecs` nested object to create test specs. Using the example above, create `tests/e2e/specs/first-folder-name/test-name-b.test.js`:
```js
/* This file was auto-generated by the command `npx wc-e2e install your-package-name`. */
import { testSecondCriticalFlow, testThirdCriticalFlow } from 'your-package-name';
testSecondCriticalFlow();
testThirdCriticalFlow();
```

View File

@ -13,10 +13,7 @@ const StreamZip = require( 'node-stream-zip' );
*/
const getRemotePluginZip = async ( fileUrl ) => {
const appPath = getAppRoot();
const savePath = path.resolve(
appPath,
'plugins/woocommerce/tests/e2e/plugins'
);
const savePath = resolveLocalE2ePath( 'plugins' );
mkdirp.sync( savePath );
// Pull the filename from the end of the URL

View File

@ -1,6 +1,6 @@
const getAppRoot = require( './app-root' );
const { getAppName, getAppBase } = require( './app-name' );
const { getTestConfig, getAdminConfig, resolveLocalE2ePath } = require( './test-config' );
const testConfig = require( './test-config' );
const { getRemotePluginZip, getLatestReleaseZipUrl } = require('./get-plugin-zip');
const takeScreenshotFor = require( './take-screenshot' );
const updateReadyPageStatus = require('./update-ready-page');
@ -10,12 +10,10 @@ module.exports = {
getAppBase,
getAppRoot,
getAppName,
getTestConfig,
getAdminConfig,
resolveLocalE2ePath,
getRemotePluginZip,
getLatestReleaseZipUrl,
takeScreenshotFor,
updateReadyPageStatus,
...testConfig,
...consoleUtils,
};

View File

@ -0,0 +1,124 @@
/**
* External dependencies.
*/
const fs = require( 'fs' );
const path = require( 'path' );
const readlineSync = require( 'readline-sync' );
/**
* Internal dependencies.
*/
const { resolveLocalE2ePath, resolvePackagePath } = require( './test-config' );
/**
* Create a path relative to the local `tests/e2e` folder.
* @param relativePath
* @return {string}
*/
const createLocalE2ePath = ( relativePath ) => {
let specFolderPath = '';
const folders = [ `..${path.sep}..${path.sep}tests`, `..${path.sep}e2e`, relativePath ];
folders.forEach( ( folder ) => {
specFolderPath = resolveLocalE2ePath( folder );
if ( ! fs.existsSync( specFolderPath ) ) {
console.log( `Creating folder ${specFolderPath}` );
fs.mkdirSync( specFolderPath );
}
} );
return specFolderPath;
};
/**
* Prompt the console for confirmation.
*
* @param {string} prompt Prompt for the user.
* @param {string} choices valid responses.
* @return {string}
*/
const confirm = ( prompt, choices ) => {
const answer = readlineSync.keyIn( prompt, choices );
return answer;
};
/**
*
* @param {string} localE2ePath Destination path
* @param {string} packageE2ePath Source path
* @param {string} packageName Source package. Default @woocommerce/e2e-environment package.
* @return {boolean}
*/
const confirmLocalCopy = ( localE2ePath, packageE2ePath, packageName = '' ) => {
const localPath = resolveLocalE2ePath( localE2ePath );
const packagePath = resolvePackagePath( packageE2ePath, packageName );
const confirmPrompt = `${localE2ePath} already exists. Overwrite? [Y]es/[n]o: `;
let overwriteFiles;
if ( fs.existsSync( localPath ) ) {
overwriteFiles = confirm( confirmPrompt, 'ny' );
overwriteFiles = overwriteFiles.toLowerCase();
} else {
overwriteFiles = 'y';
}
if ( overwriteFiles == 'y' ) {
fs.copyFileSync( packagePath, localPath );
return true;
}
return false;
};
/**
* Prompt for confirmation before deleting a local E2E file.
*
* @param {string} localE2ePath Relative path to local E2E file.
*/
const confirmLocalDelete = ( localE2ePath ) => {
const localPath = resolveLocalE2ePath( localE2ePath );
if ( ! fs.existsSync( localPath ) ) {
return;
}
const confirmPrompt = `${localE2ePath} exists. Delete? [y]es/[n]o: `;
const deleteFile = confirm( confirmPrompt, 'ny' );
if ( deleteFile == 'y' ) {
fs.unlinkSync( localPath );
}
};
/**
* Get the install data for a tests package.
*
* @param {string} packageName npm package name
* @return {string}
*/
const getPackageData = ( packageName ) => {
const packageSlug = packageName.replace( '@', '' ).replace( /\//g, '.' );
const installFiles = require( `${packageName}${path.sep}installFiles` );
return { packageSlug, ...installFiles };
};
/**
* Install test runner and test container defaults
*/
const installDefaults = () => {
createLocalE2ePath( 'docker' );
console.log( 'Writing tests/e2e/docker/initialize.sh' );
confirmLocalCopy( `docker${path.sep}initialize.sh`, `installFiles${path.sep}initialize.sh` );
createLocalE2ePath( 'config' );
console.log( 'Writing tests/e2e/config/jest.config.js' );
confirmLocalCopy( `config${path.sep}jest.config.js`, `installFiles${path.sep}jest.config.js` );
console.log( 'Writing tests/e2e/config/jest.setup.js' );
confirmLocalCopy( `config${path.sep}jest.setup.js`, `installFiles${path.sep}jest.setup.js` );
};
module.exports = {
createLocalE2ePath,
confirm,
confirmLocalCopy,
confirmLocalDelete,
getPackageData,
installDefaults,
};

View File

@ -19,15 +19,84 @@ const resolveLocalE2ePath = ( filename = '' ) => {
);
return resolvedPath;
}
};
/**
* Resolve a package name installable by npm install.
*
* @param {string} packageName Name of the installed package.
* @param {boolean} allowRecurse Allow a recursive call. Default true.
* @return {object}
*/
const resolvePackage = ( packageName, allowRecurse = true ) => {
const resolvedPackage = {};
try {
const resolvedPath = path.dirname( require.resolve( packageName ) );
const buildPaths = [ 'dist', 'build', 'build-modules' ];
// Remove build paths from the resolved path.
let resolvedParts = resolvedPath.split( path.sep );
for ( let rp = resolvedParts.length - 1; rp >= 0; rp-- ) {
if ( buildPaths.includes( resolvedParts[ rp ] ) ) {
resolvedParts = resolvedParts.slice( 0, -1 );
} else {
break;
}
}
resolvedPackage.path = resolvedParts.join( path.sep );
resolvedPackage.name = packageName;
} catch ( e ) {
// Package name installed is not the package name.
resolvedPackage.path = '';
resolvedPackage.name = '';
}
// Attempt to find the package through the project package lock file.
if ( ! resolvedPackage.path.length && allowRecurse ) {
const packageLockPath = path.resolve( appPath, 'package-lock.json' );
const packageLockContent = fs.readFileSync( packageLockPath );
const { dependencies } = JSON.parse( packageLockContent );
for ( const [ key, value ] of Object.entries( dependencies ) ) {
if ( value.version.indexOf( packageName ) == 0 ) {
resolvedPackage = resolvePackage( key, false );
break;
}
}
}
return resolvedPackage;
};
/**
* Resolve a file in a package.
*
* @param {string} filename Filename to append to the path.
* @param {string} packageName Name of the installed package. Default @woocommerce/e2e-environment.
* @return {string}
*/
const resolvePackagePath = ( filename, packageName = '' ) => {
let packagePath;
if ( ! packageName.length ) {
packagePath = path.resolve( __dirname, '../' );
} else {
const pkg = resolvePackage( packageName );
packagePath = pkg.path;
}
const resolvedPath = path.resolve(
packagePath,
filename.indexOf( '/' ) == 0 ? filename.slice( 1 ) : filename
);
return resolvedPath;
};
// Copy local test configuration file if it exists.
const localTestConfigFile = resolveLocalE2ePath( 'config/default.json' );
const defaultConfigFile = path.resolve(
__dirname,
'../config/default/default.json'
);
const testConfigFile = path.resolve( __dirname, '../config/default.json' );
const defaultConfigFile = resolvePackagePath( 'config/default/default.json' );
const testConfigFile = resolvePackagePath( 'config/default.json' );
if ( fs.existsSync( localTestConfigFile ) ) {
fs.copyFileSync( localTestConfigFile, testConfigFile );
@ -94,4 +163,6 @@ module.exports = {
getTestConfig,
getAdminConfig,
resolveLocalE2ePath,
resolvePackage,
resolvePackagePath,
};

View File

@ -78,7 +78,7 @@ export const withRestApi = {
};
const response = await client.put( onboardingProfileEndpoint, onboardingReset );
expect( response.status ).toEqual( 200 );
expect( response.statusCode ).toEqual( 200 );
},
/**
* Use api package to delete coupons.

View File

@ -21,7 +21,7 @@
"pelago/emogrifier": "3.1.0",
"psr/container": "1.0.0",
"woocommerce/action-scheduler": "3.4.0",
"woocommerce/woocommerce-admin": "2.9.3",
"woocommerce/woocommerce-admin": "3.0.0-rc.1",
"woocommerce/woocommerce-blocks": "6.3.3"
},
"require-dev": {

View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "8c4f8b290830d85dce9e69de46ca72ce",
"content-hash": "c4f41955bfde1a0a4e2d6d7428b8ee58",
"packages": [
{
"name": "automattic/jetpack-autoloader",
@ -543,16 +543,16 @@
},
{
"name": "woocommerce/woocommerce-admin",
"version": "2.9.3",
"version": "3.0.0-rc.1",
"source": {
"type": "git",
"url": "https://github.com/woocommerce/woocommerce-admin.git",
"reference": "0c4f1e637ad03178999ff53ae930b8258ca95962"
"reference": "7c0cdd01ae98be058d684dd19023b0f40094cb63"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/0c4f1e637ad03178999ff53ae930b8258ca95962",
"reference": "0c4f1e637ad03178999ff53ae930b8258ca95962",
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/7c0cdd01ae98be058d684dd19023b0f40094cb63",
"reference": "7c0cdd01ae98be058d684dd19023b0f40094cb63",
"shasum": ""
},
"require": {
@ -608,9 +608,9 @@
"homepage": "https://github.com/woocommerce/woocommerce-admin",
"support": {
"issues": "https://github.com/woocommerce/woocommerce-admin/issues",
"source": "https://github.com/woocommerce/woocommerce-admin/tree/v2.9.3"
"source": "https://github.com/woocommerce/woocommerce-admin/tree/v3.0.0-rc.1"
},
"time": "2021-12-15T03:02:58+00:00"
"time": "2021-12-14T23:55:42+00:00"
},
{
"name": "woocommerce/woocommerce-blocks",
@ -2926,5 +2926,5 @@
"platform-overrides": {
"php": "7.0.33"
},
"plugin-api-version": "2.1.0"
"plugin-api-version": "2.0.0"
}

View File

@ -149,11 +149,10 @@ class WC_Geolocation {
}
if ( empty( $ip_address ) ) {
$ip_address = self::get_ip_address();
$ip_address = self::get_ip_address();
$country_code = self::get_country_code_from_headers();
}
$country_code = self::get_country_code_from_headers();
/**
* Get geolocation filter.
*