From df582a20d3811634dbd91113d591ea8cc5971a46 Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Tue, 16 Nov 2021 11:35:30 -0400 Subject: [PATCH 01/15] add flag to distinguish between the development repo and npm package --- packages/js/e2e-environment/bin/wc-e2e.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/js/e2e-environment/bin/wc-e2e.sh b/packages/js/e2e-environment/bin/wc-e2e.sh index d5fee2dc478..29eef561c22 100755 --- a/packages/js/e2e-environment/bin/wc-e2e.sh +++ b/packages/js/e2e-environment/bin/wc-e2e.sh @@ -33,6 +33,14 @@ SCRIPTPATH=$(dirname "$0") REALPATH=$(readlink "$0") cd "$SCRIPTPATH/$(dirname "$REALPATH")/.." +# Set a flag to distinguish between the development repo and npm package +DEV_PATH=$(echo $0 | rev | cut -f4 -d/ | rev) +if [ "$DEV_PATH" != "node_modules" ]; then + export WC_E2E_WOOCOMMERCE_DEV=true +else + export WC_E2E_WOOCOMMERCE_DEV=false +fi + # Run scripts case $1 in 'docker:up') From 61fd4e0a5edfe515d8e4c85283a7e3cea73f878e Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Wed, 17 Nov 2021 09:50:40 -0400 Subject: [PATCH 02/15] introduce local E2E file resolver function --- .../js/e2e-environment/bin/docker-compose.js | 11 +- .../bin/e2e-test-integration.js | 21 +- packages/js/e2e-environment/bin/wc-e2e.sh | 8 +- .../js/e2e-environment/config/jest.config.js | 15 +- .../js/e2e-environment/docker-compose.yaml | 4 +- packages/js/e2e-environment/utils/index.js | 3 +- .../e2e-environment/utils/take-screenshot.js | 8 +- .../js/e2e-environment/utils/test-config.js | 26 ++- pnpm-lock.yaml | 220 ++++++++++++++---- 9 files changed, 228 insertions(+), 88 deletions(-) diff --git a/packages/js/e2e-environment/bin/docker-compose.js b/packages/js/e2e-environment/bin/docker-compose.js index fe9742da568..b6046773bae 100755 --- a/packages/js/e2e-environment/bin/docker-compose.js +++ b/packages/js/e2e-environment/bin/docker-compose.js @@ -10,6 +10,7 @@ const { getAppRoot, getAppName, getTestConfig, + resolveLocalE2ePath, } = require( '../utils' ); const dockerArgs = []; @@ -56,10 +57,7 @@ if ( appPath ) { const appInitFile = customInitFile ? customInitFile - : path.resolve( - appPath, - 'plugins/woocommerce/tests/e2e/docker/initialize.sh' - ); + : resolveLocalE2ePath( 'docker/initialize.sh' ); // If found, copy it into the wp-cli Docker context so // it gets picked up by the entrypoint script. if ( fs.existsSync( appInitFile ) ) { @@ -84,11 +82,6 @@ if ( ! process.env.WC_E2E_FOLDER_MAPPING ) { '/var/www/html/wp-content/plugins/' + getAppBase(); } -// Set some environment variables -if ( ! process.env.WC_CORE_PATH ) { - envVars.WC_CORE_PATH = 'plugins/woocommerce'; -} - if ( ! process.env.WORDPRESS_PORT ) { process.env.WORDPRESS_PORT = testConfig.port; } diff --git a/packages/js/e2e-environment/bin/e2e-test-integration.js b/packages/js/e2e-environment/bin/e2e-test-integration.js index 709e385333e..09f0c95dcf5 100755 --- a/packages/js/e2e-environment/bin/e2e-test-integration.js +++ b/packages/js/e2e-environment/bin/e2e-test-integration.js @@ -4,7 +4,7 @@ const { spawnSync } = require( 'child_process' ); const program = require( 'commander' ); const path = require( 'path' ); const fs = require( 'fs' ); -const { getAppRoot } = require( '../utils' ); +const { getAppRoot, resolveLocalE2ePath } = require( '../utils' ); const { WC_E2E_SCREENSHOTS, JEST_PUPPETEER_CONFIG, @@ -21,10 +21,7 @@ const appPath = getAppRoot(); // clear the screenshots folder before running tests. if ( WC_E2E_SCREENSHOTS ) { - const screenshotPath = path.resolve( - appPath, - 'plugins/woocommerce/tests/e2e/screenshots' - ); + const screenshotPath = resolveLocalE2ePath( 'screenshots' ); if ( fs.existsSync( screenshotPath ) ) { fs.readdirSync( screenshotPath ).forEach( ( file, index ) => { const filename = path.join( screenshotPath, file ); @@ -36,9 +33,7 @@ if ( WC_E2E_SCREENSHOTS ) { const nodeConfigDirs = [ path.resolve( __dirname, '../config' ) ]; if ( appPath ) { - nodeConfigDirs.unshift( - path.resolve( appPath, 'plugins/woocommerce/tests/e2e/config' ) - ); + nodeConfigDirs.unshift( resolveLocalE2ePath( 'config' ) ); } const testEnvVars = { @@ -55,10 +50,7 @@ if ( DEFAULT_TIMEOUT_OVERRIDE ) { if ( ! JEST_PUPPETEER_CONFIG ) { // Use local Puppeteer config if there is one. // Load test configuration file into an object. - const localJestConfigFile = path.resolve( - appPath, - 'tests/e2e/config/jest-puppeteer.config.js' - ); + const localJestConfigFile = resolveLocalE2ePath( 'config/jest-puppeteer.config.js' ); const jestConfigFile = path.resolve( __dirname, '../config/jest-puppeteer.config.js' @@ -100,10 +92,7 @@ let configPath = path.resolve( __dirname, '../config/jest.config.js' ); // Look for a Jest config in the dependent app's path. if ( appPath ) { - const appConfig = path.resolve( - appPath, - 'plugins/woocommerce/tests/e2e/config/jest.config.js' - ); + const appConfig = resolveLocalE2ePath( 'config/jest.config.js' ); if ( fs.existsSync( appConfig ) ) { configPath = appConfig; diff --git a/packages/js/e2e-environment/bin/wc-e2e.sh b/packages/js/e2e-environment/bin/wc-e2e.sh index 29eef561c22..11be2f572bb 100755 --- a/packages/js/e2e-environment/bin/wc-e2e.sh +++ b/packages/js/e2e-environment/bin/wc-e2e.sh @@ -36,9 +36,13 @@ cd "$SCRIPTPATH/$(dirname "$REALPATH")/.." # Set a flag to distinguish between the development repo and npm package DEV_PATH=$(echo $0 | rev | cut -f4 -d/ | rev) if [ "$DEV_PATH" != "node_modules" ]; then - export WC_E2E_WOOCOMMERCE_DEV=true + export WC_E2E_WOOCOMMERCE_DEV='true' + export WC_E2E_FOLDER='plugins/woocommerce' else - export WC_E2E_WOOCOMMERCE_DEV=false + export WC_E2E_WOOCOMMERCE_DEV='' + if [ -z $WC_E2E_FOLDER ]; then + export WC_E2E_FOLDER='' + fi fi # Run scripts diff --git a/packages/js/e2e-environment/config/jest.config.js b/packages/js/e2e-environment/config/jest.config.js index bdb0493cf4f..471007f521d 100644 --- a/packages/js/e2e-environment/config/jest.config.js +++ b/packages/js/e2e-environment/config/jest.config.js @@ -9,7 +9,7 @@ const fs = require( 'fs' ); /** * Internal Dependencies */ -const { getAppRoot } = require( '../utils' ); +const { resolveLocalE2ePath } = require( '../utils' ); const failureSetup = []; if ( WC_E2E_SCREENSHOTS ) { @@ -23,11 +23,10 @@ const setupFilesAfterEnv = [ 'expect-puppeteer', ]; -const appPath = getAppRoot(); -const localJestSetupFile = path.resolve( - appPath, - 'plugins/woocommerce/tests/e2e/config/jest.setup.js' -); +const localJestSetupFile = resolveLocalE2ePath( 'config/jest.setup.js' ); +const moduleNameMap = resolveLocalE2ePath( '$1' ); +const testSpecs = resolveLocalE2ePath( 'specs' ); + if ( fs.existsSync( localJestSetupFile ) ) { setupFilesAfterEnv.push( localJestSetupFile ); } @@ -35,7 +34,7 @@ if ( fs.existsSync( localJestSetupFile ) ) { const combinedConfig = { ...jestConfig, moduleNameMapper: { - '@woocommerce/e2e/tests/(.*)': appPath + 'tests/e2e/$1', + '@woocommerce/e2e/tests/(.*)': moduleNameMap, }, setupFiles: [ '/config/env.setup.js' ], @@ -52,7 +51,7 @@ const combinedConfig = { ...jestConfig.transformIgnorePatterns, 'node_modules/(?!(woocommerce)/)', ], - roots: [ appPath + 'tests/e2e/specs' ], + roots: [ testSpecs ], }; if ( process.env.jest_test_spec ) { diff --git a/packages/js/e2e-environment/docker-compose.yaml b/packages/js/e2e-environment/docker-compose.yaml index 52129ddf746..db7f1c91049 100644 --- a/packages/js/e2e-environment/docker-compose.yaml +++ b/packages/js/e2e-environment/docker-compose.yaml @@ -35,7 +35,7 @@ services: WORDPRESS_DEBUG: 1 volumes: - wordpress:/var/www/html - - "../../../${WC_CORE_PATH}:${WC_E2E_FOLDER_MAPPING}" + - "../../../${WC_E2E_FOLDER}:${WC_E2E_FOLDER_MAPPING}" wordpress-cli: container_name: "${APP_NAME}_wordpress-cli" @@ -60,7 +60,7 @@ services: volumes: - wordpress:/var/www/html - - "../../../plugins/woocommerce:${WC_E2E_FOLDER_MAPPING}" + - "../../../${WC_E2E_FOLDER}:${WC_E2E_FOLDER_MAPPING}" volumes: db: diff --git a/packages/js/e2e-environment/utils/index.js b/packages/js/e2e-environment/utils/index.js index 3a3e220c88c..baf54dc1b82 100644 --- a/packages/js/e2e-environment/utils/index.js +++ b/packages/js/e2e-environment/utils/index.js @@ -1,6 +1,6 @@ const getAppRoot = require( './app-root' ); const { getAppName, getAppBase } = require( './app-name' ); -const { getTestConfig, getAdminConfig } = require( './test-config' ); +const { getTestConfig, getAdminConfig, resolveLocalE2ePath } = require( './test-config' ); const { getRemotePluginZip, getLatestReleaseZipUrl } = require('./get-plugin-zip'); const takeScreenshotFor = require( './take-screenshot' ); const updateReadyPageStatus = require('./update-ready-page'); @@ -12,6 +12,7 @@ module.exports = { getAppName, getTestConfig, getAdminConfig, + resolveLocalE2ePath, getRemotePluginZip, getLatestReleaseZipUrl, takeScreenshotFor, diff --git a/packages/js/e2e-environment/utils/take-screenshot.js b/packages/js/e2e-environment/utils/take-screenshot.js index dfe9b500e70..b76db7c2e4c 100644 --- a/packages/js/e2e-environment/utils/take-screenshot.js +++ b/packages/js/e2e-environment/utils/take-screenshot.js @@ -1,6 +1,6 @@ const path = require( 'path' ); const mkdirp = require( 'mkdirp' ); -const getAppRoot = require( './app-root' ); +const { resolveLocalE2ePath } = require( './test-config' ); /** * Take a screenshot if browser context exists. @@ -10,11 +10,7 @@ const getAppRoot = require( './app-root' ); */ const takeScreenshotFor = async ( message ) => { const title = message.replace( /\.$/, '' ); - const appPath = getAppRoot(); - const savePath = path.resolve( - appPath, - 'plugins/woocommerce/tests/e2e/screenshots' - ); + const savePath = resolveLocalE2ePath( 'screenshots' ); const filePath = path.join( savePath, `${ title }.png`.replace( /[^a-z0-9.-]+/gi, '-' ) diff --git a/packages/js/e2e-environment/utils/test-config.js b/packages/js/e2e-environment/utils/test-config.js index 0f423db30d9..2f4f187e5c3 100644 --- a/packages/js/e2e-environment/utils/test-config.js +++ b/packages/js/e2e-environment/utils/test-config.js @@ -2,12 +2,27 @@ const path = require( 'path' ); const fs = require( 'fs' ); const getAppRoot = require( './app-root' ); -// Copy local test configuration file if it exists. const appPath = getAppRoot(); -const localTestConfigFile = path.resolve( - appPath, - 'plugins/woocommerce/tests/e2e/config/default.json' -); + +/** + * Resolve a local E2E file. + * + * @param {string} filename Filename to append to the path. + * @return {string} + */ +const resolveLocalE2ePath = ( filename = '' ) => { + const { WC_E2E_FOLDER } = process.env; + const localPath = `${WC_E2E_FOLDER}/tests/e2e/${filename}`; + const resolvedPath = path.resolve( + appPath, + localPath.indexOf( '/' ) == 0 ? localPath.slice( 1 ) : localPath + ); + + 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' @@ -78,4 +93,5 @@ const getAdminConfig = () => { module.exports = { getTestConfig, getAdminConfig, + resolveLocalE2ePath, }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ad7efa07b34..46093cd3052 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,11 +34,11 @@ importers: wp-textdomain: 1.0.1 devDependencies: '@automattic/nx-composer': 0.1.0 - '@nrwl/cli': 13.1.3 + '@nrwl/cli': 13.1.4 '@nrwl/linter': 13.1.3 - '@nrwl/tao': 13.1.3 + '@nrwl/tao': 13.1.4 '@nrwl/web': 13.1.3_42cab1dece2b2240094de84cfd414406 - '@nrwl/workspace': 13.1.3_wp-prettier@2.2.1-beta-1 + '@nrwl/workspace': 13.1.4_wp-prettier@2.2.1-beta-1 '@types/node': 14.14.33 '@woocommerce/eslint-plugin': 1.3.0 '@wordpress/prettier-config': 1.1.1 @@ -159,12 +159,9 @@ importers: packages/js/e2e-utils: specifiers: -<<<<<<< HEAD '@automattic/puppeteer-utils': github:Automattic/puppeteer-utils#0f3ec50 -======= '@typescript-eslint/eslint-plugin': ^5.3.0 '@typescript-eslint/parser': ^5.3.0 ->>>>>>> trunk '@wordpress/deprecated': ^2.10.0 '@wordpress/e2e-test-utils': ^4.16.1 config: 3.3.3 @@ -469,7 +466,7 @@ packages: '@babel/compat-data': 7.15.0 '@babel/core': 7.15.8 '@babel/helper-validator-option': 7.14.5 - browserslist: 4.17.6 + browserslist: 4.17.3 semver: 6.3.0 /@babel/helper-create-class-features-plugin/7.15.4_@babel+core@7.12.9: @@ -489,6 +486,22 @@ packages: - supports-color dev: true + /@babel/helper-create-class-features-plugin/7.15.4_@babel+core@7.15.8: + resolution: {integrity: sha512-7ZmzFi+DwJx6A7mHRwbuucEYpyBwmh2Ca0RvI6z2+WLZYCqV0JOaLb+u0zbtmDicebgKBZgqbYfLaKNqSgv5Pw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.15.8 + '@babel/helper-annotate-as-pure': 7.15.4 + '@babel/helper-function-name': 7.15.4 + '@babel/helper-member-expression-to-functions': 7.15.4 + '@babel/helper-optimise-call-expression': 7.15.4 + '@babel/helper-replace-supers': 7.15.4 + '@babel/helper-split-export-declaration': 7.15.4 + transitivePeerDependencies: + - supports-color + /@babel/helper-create-class-features-plugin/7.16.0_@babel+core@7.15.8: resolution: {integrity: sha512-XLwWvqEaq19zFlF5PTgOod4bUA+XbkR4WLQBct1bkzmxJGB0ZEJaoKF4c8cgH9oBtCDuYJ8BP5NB9uFiEgO5QA==} engines: {node: '>=6.9.0'} @@ -523,7 +536,7 @@ packages: '@babel/core': ^7.0.0 dependencies: '@babel/core': 7.15.8 - '@babel/helper-annotate-as-pure': 7.16.0 + '@babel/helper-annotate-as-pure': 7.15.4 regexpu-core: 4.8.0 /@babel/helper-define-polyfill-provider/0.2.3_@babel+core@7.15.8: @@ -588,7 +601,6 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.15.6 - dev: true /@babel/helper-member-expression-to-functions/7.16.0: resolution: {integrity: sha512-bsjlBFPuWT6IWhl28EdrQ+gTvSvj5tqVP5Xeftp07SEuz5pLnsXZuDkDD3Rfcxy0IsHmbZ+7B2/9SHzxO0T+sQ==} @@ -622,7 +634,6 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.15.6 - dev: true /@babel/helper-optimise-call-expression/7.16.0: resolution: {integrity: sha512-SuI467Gi2V8fkofm2JPnZzB/SUuXoJA5zXe/xzyPP2M04686RzFKFHPK6HDVN6JvWBIEW8tt9hPR7fXdn2Lgpw==} @@ -713,9 +724,9 @@ packages: resolution: {integrity: sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/template': 7.15.4 - '@babel/traverse': 7.15.4 - '@babel/types': 7.15.6 + '@babel/template': 7.16.0 + '@babel/traverse': 7.16.0 + '@babel/types': 7.16.0 transitivePeerDependencies: - supports-color @@ -795,7 +806,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.15.8 - '@babel/helper-create-class-features-plugin': 7.16.0_@babel+core@7.15.8 + '@babel/helper-create-class-features-plugin': 7.15.4_@babel+core@7.15.8 '@babel/helper-plugin-utils': 7.14.5 transitivePeerDependencies: - supports-color @@ -1044,7 +1055,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.15.8 - '@babel/helper-create-class-features-plugin': 7.16.0_@babel+core@7.15.8 + '@babel/helper-create-class-features-plugin': 7.15.4_@babel+core@7.15.8 '@babel/helper-plugin-utils': 7.14.5 transitivePeerDependencies: - supports-color @@ -1470,12 +1481,12 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.15.8 - '@babel/helper-annotate-as-pure': 7.16.0 - '@babel/helper-function-name': 7.16.0 - '@babel/helper-optimise-call-expression': 7.16.0 + '@babel/helper-annotate-as-pure': 7.15.4 + '@babel/helper-function-name': 7.15.4 + '@babel/helper-optimise-call-expression': 7.15.4 '@babel/helper-plugin-utils': 7.14.5 - '@babel/helper-replace-supers': 7.16.0 - '@babel/helper-split-export-declaration': 7.16.0 + '@babel/helper-replace-supers': 7.15.4 + '@babel/helper-split-export-declaration': 7.15.4 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -1616,7 +1627,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.15.8 - '@babel/helper-function-name': 7.16.0 + '@babel/helper-function-name': 7.15.4 '@babel/helper-plugin-utils': 7.14.5 /@babel/plugin-transform-literals/7.14.5_@babel+core@7.12.9: @@ -1736,7 +1747,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.15.8 - '@babel/helper-hoist-variables': 7.16.0 + '@babel/helper-hoist-variables': 7.15.4 '@babel/helper-module-transforms': 7.15.8 '@babel/helper-plugin-utils': 7.14.5 '@babel/helper-validator-identifier': 7.15.7 @@ -1828,7 +1839,7 @@ packages: dependencies: '@babel/core': 7.15.8 '@babel/helper-plugin-utils': 7.14.5 - '@babel/helper-replace-supers': 7.16.0 + '@babel/helper-replace-supers': 7.15.4 transitivePeerDependencies: - supports-color @@ -2276,7 +2287,7 @@ packages: '@babel/helper-plugin-utils': 7.14.5 '@babel/plugin-proposal-unicode-property-regex': 7.14.5_@babel+core@7.15.8 '@babel/plugin-transform-dotall-regex': 7.14.5_@babel+core@7.15.8 - '@babel/types': 7.16.0 + '@babel/types': 7.15.6 esutils: 2.0.3 /@babel/preset-typescript/7.15.0_@babel+core@7.15.8: @@ -3178,6 +3189,18 @@ packages: yargs-parser: 20.0.0 dev: true + /@nrwl/cli/13.1.4: + resolution: {integrity: sha512-X2mJwjXitBbY/zRkcVJCUI4Kwk7bPJ/ZJwZHQH5Sn9IAX3p13ELQ1eAanxpOpUakVLvkjlAJ1g5vzt1znzs8wA==} + hasBin: true + dependencies: + '@nrwl/tao': 13.1.4 + chalk: 4.1.0 + enquirer: 2.3.6 + v8-compile-cache: 2.3.0 + yargs: 15.4.1 + yargs-parser: 20.0.0 + dev: true + /@nrwl/cypress/13.1.3_66b6d5e7c0d0fab5f0fa780d7c17d7d6: resolution: {integrity: sha512-lM/q0AdhUSTIWO/elSlVdjXztMDB0z7b818MeeD5I/dDEUCzIg/GDhcWigCiYfOys2MwBSOz++FrMfkNT8Z+Cw==} peerDependencies: @@ -3237,6 +3260,17 @@ packages: tslib: 2.3.1 dev: true + /@nrwl/devkit/13.1.4: + resolution: {integrity: sha512-2jzb7A94O8k3lQfIvCgVb/LPoym2P6EoKAEYYX6OgPY0hwjaqik1LgkWxSnN0yTPL5gCCxb6pYLHS8A0tDye2w==} + dependencies: + '@nrwl/tao': 13.1.4 + ejs: 3.1.6 + ignore: 5.1.8 + rxjs: 6.6.7 + semver: 7.3.4 + tslib: 2.3.1 + dev: true + /@nrwl/jest/13.1.3: resolution: {integrity: sha512-6ogg6TOOneVtJuIW02wKrkO35EDGtpiuIdB58syQOYbG3SwqsHvy0MPGqzz+A1q4esOE3+qyn/9M+DtTGiAwbQ==} dependencies: @@ -3259,6 +3293,28 @@ packages: - utf-8-validate dev: true + /@nrwl/jest/13.1.4: + resolution: {integrity: sha512-Lb+jVgHhamnO/kkJpRbgr7lvLGh4pqgp+WXzcDJo8in0TgWcdZzldCfV7lXiFoRVv1FgUjVYb5BgfNcVsrDJLg==} + dependencies: + '@jest/reporters': 27.2.2 + '@jest/test-result': 27.2.2 + '@nrwl/devkit': 13.1.4 + chalk: 4.1.0 + identity-obj-proxy: 3.0.0 + jest-config: 27.2.2 + jest-resolve: 27.2.2 + jest-util: 27.2.0 + rxjs: 6.6.7 + tslib: 2.3.1 + transitivePeerDependencies: + - bufferutil + - canvas + - node-notifier + - supports-color + - ts-node + - utf-8-validate + dev: true + /@nrwl/linter/13.1.3: resolution: {integrity: sha512-aUIHKenNTZxqv3RZ2KVgMuZRAoyDIowp0r8qoH+Xfhxkq+PpLHDsk+Z3C0LDzk7sbqQeIbq+bbSleUtHYDh5qg==} dependencies: @@ -3278,6 +3334,25 @@ packages: - utf-8-validate dev: true + /@nrwl/linter/13.1.4: + resolution: {integrity: sha512-eeBP2BOA8U7QpDbWcYQ7d30I9oSXsxl7jZnhggRUDxmrW1SzJmMTXCSAwRLFnHediFAYQVR1FxVmIjX8cxRPBQ==} + dependencies: + '@nrwl/devkit': 13.1.4 + '@nrwl/jest': 13.1.4 + eslint: 7.32.0 + glob: 7.1.4 + minimatch: 3.0.4 + tmp: 0.2.1 + tslib: 2.3.1 + transitivePeerDependencies: + - bufferutil + - canvas + - node-notifier + - supports-color + - ts-node + - utf-8-validate + dev: true + /@nrwl/tao/12.10.0: resolution: {integrity: sha512-YkdgTJJsDQlItVj25vW8zEen7BAra6i41Udd0v3CuxTSEXjJJnBD2KzEOGUxXS0gMg7+ILuw2rl9aOKu43TmVA==} hasBin: true @@ -3312,6 +3387,23 @@ packages: yargs-parser: 20.0.0 dev: true + /@nrwl/tao/13.1.4: + resolution: {integrity: sha512-XslTN56x5Y1sEuVkGoAMCibEU0V5CunOORSewMWsNaEWtefhkLD00R0L02Uj4q1d28H+6TiucjR/mGFjyEzWUQ==} + hasBin: true + dependencies: + chalk: 4.1.0 + enquirer: 2.3.6 + fs-extra: 9.1.0 + jsonc-parser: 3.0.0 + nx: 13.1.4 + rxjs: 6.6.7 + rxjs-for-await: 0.0.2_rxjs@6.6.7 + semver: 7.3.4 + tmp: 0.2.1 + tslib: 2.3.1 + yargs-parser: 20.0.0 + dev: true + /@nrwl/web/13.1.3_42cab1dece2b2240094de84cfd414406: resolution: {integrity: sha512-Tt9pWvOg1UWPIWDk4X745UOqmSMEbUlKQ0L1GA9oyeH5dJ9DPEzwXzLfyCZ4aXAXdgZxqnV77Pa4qOVwxL5Jpg==} dependencies: @@ -3465,6 +3557,49 @@ packages: - utf-8-validate dev: true + /@nrwl/workspace/13.1.4_wp-prettier@2.2.1-beta-1: + resolution: {integrity: sha512-dQlxswf2XlMyEJBK4+fZHQTpxtevcWzDCVO9iLjuvL1XZDbDQG95+N7DsASq67qOxFAlpYWVxAVZAXIHsnX9tQ==} + peerDependencies: + prettier: ^2.3.0 + peerDependenciesMeta: + prettier: + optional: true + dependencies: + '@nrwl/cli': 13.1.4 + '@nrwl/devkit': 13.1.4 + '@nrwl/jest': 13.1.4 + '@nrwl/linter': 13.1.4 + '@parcel/watcher': 2.0.0-alpha.11 + chalk: 4.1.0 + chokidar: 3.5.2 + cosmiconfig: 4.0.0 + dotenv: 10.0.0 + enquirer: 2.3.6 + flat: 5.0.2 + fs-extra: 9.1.0 + glob: 7.1.4 + ignore: 5.1.8 + minimatch: 3.0.4 + npm-run-all: 4.1.5 + npm-run-path: 4.0.1 + open: 7.4.2 + prettier: /wp-prettier/2.2.1-beta-1 + rxjs: 6.6.7 + semver: 7.3.4 + strip-ansi: 6.0.0 + tmp: 0.2.1 + tslib: 2.3.1 + yargs: 15.4.1 + yargs-parser: 20.0.0 + transitivePeerDependencies: + - bufferutil + - canvas + - node-notifier + - supports-color + - ts-node + - utf-8-validate + dev: true + /@parcel/watcher/2.0.0-alpha.11: resolution: {integrity: sha512-zMIAsFLcnB82kkk0kSOZ/zgyihb8sty0zVrsz+3ruoYXkchymWsCDsxiX4v+X2s8Jppk3JE8vlnD4DKs3QTOEQ==} engines: {node: '>= 10.0.0'} @@ -5673,10 +5808,10 @@ packages: peerDependencies: eslint: '>= 4.12.1' dependencies: - '@babel/code-frame': 7.16.0 - '@babel/parser': 7.16.2 - '@babel/traverse': 7.16.0 - '@babel/types': 7.16.0 + '@babel/code-frame': 7.15.8 + '@babel/parser': 7.15.8 + '@babel/traverse': 7.15.4 + '@babel/types': 7.15.6 eslint: 7.32.0 eslint-visitor-keys: 1.3.0 resolve: 1.20.0 @@ -8789,7 +8924,7 @@ packages: engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1} hasBin: true dependencies: - '@babel/code-frame': 7.15.8 + '@babel/code-frame': 7.16.0 ajv: 6.12.6 chalk: 2.4.2 cross-spawn: 6.0.5 @@ -11601,11 +11736,11 @@ packages: resolution: {integrity: sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==} engines: {node: '>=6'} dependencies: - '@babel/generator': 7.15.8 - '@babel/parser': 7.15.8 - '@babel/template': 7.15.4 - '@babel/traverse': 7.15.4 - '@babel/types': 7.15.6 + '@babel/generator': 7.16.0 + '@babel/parser': 7.16.2 + '@babel/template': 7.16.0 + '@babel/traverse': 7.16.0 + '@babel/types': 7.16.0 istanbul-lib-coverage: 2.0.5 semver: 6.3.0 transitivePeerDependencies: @@ -12262,7 +12397,7 @@ packages: resolution: {integrity: sha512-Cq7vkAgaYKp+PsX+2/JbTarrk0DmNhsEtqBXNwUHkdlbrTBLtMJINADf2mf5FkowNsq8evbPc07/qFO0AdKTzw==} engines: {node: '>= 6'} dependencies: - '@babel/traverse': 7.15.4 + '@babel/traverse': 7.16.0 '@jest/environment': 24.9.0 '@jest/test-result': 24.9.0 '@jest/types': 24.9.0 @@ -12400,7 +12535,7 @@ packages: resolution: {integrity: sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==} engines: {node: '>= 6'} dependencies: - '@babel/code-frame': 7.15.8 + '@babel/code-frame': 7.16.0 '@jest/test-result': 24.9.0 '@jest/types': 24.9.0 '@types/stack-utils': 1.0.1 @@ -12876,7 +13011,7 @@ packages: resolution: {integrity: sha512-uI/rszGSs73xCM0l+up7O7a40o90cnrk429LOiK3aeTvfC0HHmldbd81/B7Ix81KSFe1lwkbl7GnBGG4UfuDew==} engines: {node: '>= 6'} dependencies: - '@babel/types': 7.15.6 + '@babel/types': 7.16.0 '@jest/types': 24.9.0 chalk: 2.4.2 expect: 24.9.0 @@ -14676,14 +14811,21 @@ packages: resolution: {integrity: sha512-LpCfZCWsVEtmD2SI1j2KRaw1uIyn4DJ3eRzsjnDYitbq38aORpkvYO+L0zVMZRNDSYSRGTsuj0nHCS3OOxK/Cg==} hasBin: true dependencies: - '@nrwl/cli': 13.1.3 + '@nrwl/cli': 13.1.4 dev: true /nx/13.1.3: resolution: {integrity: sha512-clM0NQhQKYkqcNz2E3uYRMLwhp2L/9dBhJhQi9XBX4IAyA2gWAomhRIlLm5Xxg3g4h1xwSpP3eJ5t89VikY8Pw==} hasBin: true dependencies: - '@nrwl/cli': 13.1.3 + '@nrwl/cli': 13.1.4 + dev: true + + /nx/13.1.4: + resolution: {integrity: sha512-m2j3wymaFlEl/7EoGxlgRzdmgQV1Rsh42df1cM8xFzAzV8ZGtR3Zq19qK7r9SUabpq8jMzp1e6rLQTHewCJWig==} + hasBin: true + dependencies: + '@nrwl/cli': 13.1.4 dev: true /oauth-1.0a/2.2.6: From 43d73eb32af1f6128f8d02d28c0104855133b162 Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Wed, 17 Nov 2021 10:02:15 -0400 Subject: [PATCH 03/15] update package changelog and readme --- packages/js/e2e-environment/CHANGELOG.md | 2 ++ packages/js/e2e-environment/builtin.md | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/js/e2e-environment/CHANGELOG.md b/packages/js/e2e-environment/CHANGELOG.md index 4c1db86b56f..a953ce33963 100644 --- a/packages/js/e2e-environment/CHANGELOG.md +++ b/packages/js/e2e-environment/CHANGELOG.md @@ -5,6 +5,8 @@ - Added quotes around `WORDPRESS_TITLE` value in .env file to address issue with docker compose 2 "key cannot contain a space" error. - Added `LATEST_WP_VERSION_MINUS` that allows setting a number to subtract from the current WordPress version for the WordPress Docker image. - Support for PHP_VERSION, MARIADB_VERSION environment variables for built in container initialization +- `resolveLocalE2ePath` to resolve path to local E2E file +- `WC_E2E_FOLDER` for mapping plugin root to path within repo ## Fixed diff --git a/packages/js/e2e-environment/builtin.md b/packages/js/e2e-environment/builtin.md index 6ec0095f7c9..2b91b80bd98 100644 --- a/packages/js/e2e-environment/builtin.md +++ b/packages/js/e2e-environment/builtin.md @@ -119,7 +119,12 @@ You can override these in `/tests/e2e/config/default.json`. ### Folder Mapping -The built in container defaults to mapping the root folder of the repository to a folder in the `plugins` folder. For example `woocommerce` is mapped to `/var/www/html/wp-content/plugins/woocommerce`. Use the `WC_E2E_FOLDER_MAPPING` environment variable to override this mapping. +The built in container defaults to mapping the root folder of the repository to a folder in the `plugins` folder. Use the environment variables `WC_E2E_FOLDER` and `WC_E2E_FOLDER_MAPPING` to override this mapping. The `WC_E2E_FOLDER` is a path relative to the root of the project. For example, in the `woocommerce` repository this mapping is: + +- `WC_E2E_FOLDER=plugins/woocommerce` +- `WC_E2E_FOLDER_MAPPING=/var/www/html/wp-content/plugins/woocommerce` + +Other repository examples: - Storefront Theme - ```WC_E2E_FOLDER_MAPPING=/var/www/html/wp-content/themes/storefront npx wc-e2e docker:up``` - Site Project - ```WC_E2E_FOLDER_MAPPING=/var/www/html/wp-content npx wc-e2e docker:up``` From b698a80f36cda83801bbcfe7a00d925738cbe9bd Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Fri, 19 Nov 2021 17:02:02 -0400 Subject: [PATCH 04/15] add basic test spec installer --- .../data/default-test-config.json | 195 ++++++++++++++++++ .../e2e-core-tests/data/scaffold-tests.json | 138 +++++++++++++ packages/js/e2e-core-tests/index.js | 18 +- packages/js/e2e-environment/bin/scaffold.js | 137 ++++++++++++ packages/js/e2e-environment/bin/wc-e2e.sh | 3 + packages/js/e2e-environment/package.json | 3 +- pnpm-lock.yaml | 113 +++++++++- 7 files changed, 594 insertions(+), 13 deletions(-) create mode 100644 packages/js/e2e-core-tests/data/default-test-config.json create mode 100644 packages/js/e2e-core-tests/data/scaffold-tests.json create mode 100755 packages/js/e2e-environment/bin/scaffold.js diff --git a/packages/js/e2e-core-tests/data/default-test-config.json b/packages/js/e2e-core-tests/data/default-test-config.json new file mode 100644 index 00000000000..c67465ed4ba --- /dev/null +++ b/packages/js/e2e-core-tests/data/default-test-config.json @@ -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" + } + } + } +} diff --git a/packages/js/e2e-core-tests/data/scaffold-tests.json b/packages/js/e2e-core-tests/data/scaffold-tests.json new file mode 100644 index 00000000000..7d95b03587d --- /dev/null +++ b/packages/js/e2e-core-tests/data/scaffold-tests.json @@ -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" } + ] + } + ] +} + + diff --git a/packages/js/e2e-core-tests/index.js b/packages/js/e2e-core-tests/index.js index 9fd036edd21..949cffc0690 100644 --- a/packages/js/e2e-core-tests/index.js +++ b/packages/js/e2e-core-tests/index.js @@ -2,5 +2,21 @@ * Internal dependencies */ const allSpecs = require( './specs' ); +const fs = require( 'fs' ); -module.exports = allSpecs; +/** + * Read test set up configuration for the test scaffolding tool. + */ +const getObjectFromJsonFile = ( filename ) => { + const specs = fs.readFileSync( filename ); + return JSON.parse( specs ); +}; + +const getTestInstallSpecs = () => getObjectFromJsonFile( './data/install-specs.json' ); +const getSampleDefaultJson = () => getObjectFromJsonFile( './data/default-test-config.json' ); + +module.exports = { + allSpecs, + getTestInstallSpecs, + getSampleDefaultJson, +}; diff --git a/packages/js/e2e-environment/bin/scaffold.js b/packages/js/e2e-environment/bin/scaffold.js new file mode 100755 index 00000000000..4594f6c8e76 --- /dev/null +++ b/packages/js/e2e-environment/bin/scaffold.js @@ -0,0 +1,137 @@ +#!/usr/bin/env node + +/** + * External dependencies. + */ +const fs = require( 'fs' ); +const sprintf = require( 'sprintf-js' ).sprintf; + +/** + * Internal dependencies. + */ +const { resolveLocalE2ePath } = require( '../utils' ); + +const [ command, package ] = process.argv.slice( 2 ); + +/** + * Install the test scripts and sample default.json configuration + */ +if ( command == 'install' ) { + /** + const { getTestInstallSpecs, getSampleDefaultJson } = require( package ); + + if ( getTestInstallSpecs instanceof Function ) { + console.log( 'is a function' ); + } + if ( getSampleDefaultJson instanceof Function ) { + const sampleDefaultJson = getSampleDefaultJson(); + console.log( sampleDefaultJson ); + } + /**/ + // @todo Add logic for dynamic file extension + const testExtension = 'test.js'; + const sampleDefaultJson = { + "url": "http://localhost:8084/", + "users": { + "admin": { + "username": "admin", + "password": "password" + }, + "customer": { + "username": "customer", + "password": "password" + } + }, + }; + const testSpecs = { + "active": [ + { + "name": "example", + "description": "Shopper tests", + "testFiles": [ + { + "name": "cart-begin", + "functions": [ "runCartPageTest" ] + }, { + "name": "cart-end", + "functions": [ + "testCartEnd", + "testCartEndPart2", + "testCartEndPart3", + "testCartEndPart4" + ] + } + ] + } + ] + }; + + // Write sample default.json + const packageSlug = package.replace( '@', '' ).replace( /\//g, '.' ); + const defaultJsonName = `config/default-${packageSlug}.json`; + const defaultJsonSample = resolveLocalE2ePath( defaultJsonName ); + fs.writeFileSync( defaultJsonSample, JSON.stringify( sampleDefaultJson , null, 2 ) ); + console.log( `\nWrote sample test configuration to 'tests/e2e/${defaultJsonName}'.\n` ); + + // Write test files + if ( testSpecs.active && testSpecs.active.length ) { + const eol = "\n"; + const autoGenerate = sprintf( '/* This file was auto-generated by the command `npx wc-e2e install %s. */', package ); + const importLineFormat = sprintf( "import {%%s} from '%s';", package ); + // Loop through folders and files to write test scripts. + for ( let f = 0; f < testSpecs.active.length; f++ ) { + const testFolder = testSpecs.active[ f ]; + if ( ! testFolder.testFiles || ! testFolder.testFiles.length ) { + continue; + } + + const specFolder = testFolder.name.length ? `specs/${testFolder.name}` : 'specs'; + const specFolderPath = resolveLocalE2ePath( specFolder ); + + // Create the test folder if it doesn't exist. + if ( ! fs.existsSync( specFolderPath) ) { + fs.mkdirSync( specFolderPath ); + } + + // Create the test files. + for ( let t = 0; t < testFolder.testFiles.length; t++ ) { + const testFile = testFolder.testFiles[ t ]; + if ( ! testFile.functions.length ) { + continue; + } + + const testFileName = `${specFolder}/${testFile.name}.${testExtension}`; + const testFilePath = resolveLocalE2ePath( testFileName ); + + // @todo Add check to see if file exists + // fs.existsSync + // @todo Confirm overwrite + // readline + + console.log( 'Writing tests/e2e/' + 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; + // '' adds a blank line + buffer.push( sprintf( importLineFormat, importPrefix + testImport ), '' ); + + // Add test function calls and write the file + let functionCalls = testFile.functions.map( functionName => functionName + '();' ); + buffer.push( ...functionCalls ); + fs.writeFileSync( testFilePath, buffer.join( eol ) ); + } + console.log( eol ); + } + } +} diff --git a/packages/js/e2e-environment/bin/wc-e2e.sh b/packages/js/e2e-environment/bin/wc-e2e.sh index 11be2f572bb..bbba17c2802 100755 --- a/packages/js/e2e-environment/bin/wc-e2e.sh +++ b/packages/js/e2e-environment/bin/wc-e2e.sh @@ -71,6 +71,9 @@ case $1 in ./bin/wait-for-build.sh && ./bin/e2e-test-integration.js --dev --debug $2 TESTRESULT=$? ;; + 'install') + ./bin/scaffold.js $@ + ;; *) usage ;; diff --git a/packages/js/e2e-environment/package.json b/packages/js/e2e-environment/package.json index 33e576f5420..47b12b89ba5 100644 --- a/packages/js/e2e-environment/package.json +++ b/packages/js/e2e-environment/package.json @@ -32,8 +32,9 @@ "jest": "^25.1.0", "jest-each": "25.5.0", "jest-puppeteer": "^4.4.0", + "node-stream-zip": "^1.13.6", "request": "^2.88.2", - "node-stream-zip": "^1.13.6" + "sprintf-js": "^1.1.2" }, "devDependencies": { "@babel/cli": "7.12.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cee9c417779..35b3bc23251 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,11 +34,11 @@ importers: wp-textdomain: 1.0.1 devDependencies: '@automattic/nx-composer': 0.1.0 - '@nrwl/cli': 13.1.4 + '@nrwl/cli': 13.2.0 '@nrwl/linter': 13.1.4 - '@nrwl/tao': 13.1.4 + '@nrwl/tao': 13.2.0 '@nrwl/web': 13.1.4_42cab1dece2b2240094de84cfd414406 - '@nrwl/workspace': 13.1.4_wp-prettier@2.2.1-beta-1 + '@nrwl/workspace': 13.2.0_wp-prettier@2.2.1-beta-1 '@types/node': 14.14.33 '@woocommerce/eslint-plugin': 1.3.0 '@wordpress/prettier-config': 1.1.1 @@ -129,6 +129,7 @@ importers: node-stream-zip: ^1.13.6 request: ^2.88.2 semver: ^7.3.2 + sprintf-js: ^1.1.2 dependencies: '@automattic/puppeteer-utils': github.com/Automattic/puppeteer-utils/0f3ec50 '@jest/test-sequencer': 25.5.4 @@ -143,6 +144,7 @@ importers: jest-puppeteer: 4.4.0 node-stream-zip: 1.15.0 request: 2.88.2 + sprintf-js: 1.1.2 devDependencies: '@babel/cli': 7.12.8_@babel+core@7.12.9 '@babel/core': 7.12.9 @@ -4249,6 +4251,18 @@ packages: yargs-parser: 20.0.0 dev: true + /@nrwl/cli/13.2.0: + resolution: {integrity: sha512-zpDkt+e1cZS+/3IVJLXcmwwvy/vnW/FAQdSecvrEkYZPIMFfVFlL6814RebuIyg5LKak5rdqIPp+ndv5quTkzg==} + hasBin: true + dependencies: + '@nrwl/tao': 13.2.0 + chalk: 4.1.0 + enquirer: 2.3.6 + v8-compile-cache: 2.3.0 + yargs: 15.4.1 + yargs-parser: 20.0.0 + dev: true + /@nrwl/cypress/13.1.4_45e5b05f8550ddf8ad44a90a38658752: resolution: {integrity: sha512-t3HewJdWl7wWAoFm6ZCtMbe8AODvV7Gtck4RP/cB0lFn6Di0q8tUNutVXSfNSdAvSURvegurZsyb30ehIM1FcQ==} peerDependencies: @@ -4308,6 +4322,17 @@ packages: tslib: 2.3.1 dev: true + /@nrwl/devkit/13.2.0: + resolution: {integrity: sha512-CaFepGKDxeH5esoQzj92Q6tAlcUjhZTxXWTOZ5D/2JeC5sdIaWqPJ5G3jeKm9zODbv9f1Q3L+Zv1ZDULHTQtCw==} + dependencies: + '@nrwl/tao': 13.2.0 + ejs: 3.1.6 + ignore: 5.1.9 + rxjs: 6.6.7 + semver: 7.3.4 + tslib: 2.3.1 + dev: true + /@nrwl/jest/13.1.4: resolution: {integrity: sha512-Lb+jVgHhamnO/kkJpRbgr7lvLGh4pqgp+WXzcDJo8in0TgWcdZzldCfV7lXiFoRVv1FgUjVYb5BgfNcVsrDJLg==} dependencies: @@ -4352,6 +4377,29 @@ packages: - utf-8-validate dev: true + /@nrwl/jest/13.2.0: + resolution: {integrity: sha512-vnc+vtTzTZGG057cOkBGKhRc6ZQQtdWsF8KzZ3KTAlH5u4l7BetQ6borogcMuCTFUzzB30Aj6Gl3Hyc5CSwGuA==} + dependencies: + '@jest/reporters': 27.2.2 + '@jest/test-result': 27.2.2 + '@nrwl/devkit': 13.2.0 + chalk: 4.1.0 + identity-obj-proxy: 3.0.0 + jest-config: 27.2.2 + jest-resolve: 27.2.2 + jest-util: 27.2.0 + resolve.exports: 1.1.0 + rxjs: 6.6.7 + tslib: 2.3.1 + transitivePeerDependencies: + - bufferutil + - canvas + - node-notifier + - supports-color + - ts-node + - utf-8-validate + dev: true + /@nrwl/linter/13.1.4: resolution: {integrity: sha512-eeBP2BOA8U7QpDbWcYQ7d30I9oSXsxl7jZnhggRUDxmrW1SzJmMTXCSAwRLFnHediFAYQVR1FxVmIjX8cxRPBQ==} dependencies: @@ -4390,6 +4438,25 @@ packages: - utf-8-validate dev: true + /@nrwl/linter/13.2.0: + resolution: {integrity: sha512-cNEpL1j/6wZswxY4oGY2p3JurwHCrJ2jfKGuEeNbqlYcVgt+BFHTkzGdgBepFo2z1/mbj2DneJlGZCK19RrtRQ==} + dependencies: + '@nrwl/devkit': 13.2.0 + '@nrwl/jest': 13.2.0 + eslint: 7.32.0 + glob: 7.1.4 + minimatch: 3.0.4 + tmp: 0.2.1 + tslib: 2.3.1 + transitivePeerDependencies: + - bufferutil + - canvas + - node-notifier + - supports-color + - ts-node + - utf-8-validate + dev: true + /@nrwl/tao/12.10.0: resolution: {integrity: sha512-YkdgTJJsDQlItVj25vW8zEen7BAra6i41Udd0v3CuxTSEXjJJnBD2KzEOGUxXS0gMg7+ILuw2rl9aOKu43TmVA==} hasBin: true @@ -4424,6 +4491,23 @@ packages: yargs-parser: 20.0.0 dev: true + /@nrwl/tao/13.2.0: + resolution: {integrity: sha512-1mxEJ9o3DftTteMaLVcNBewnfkYafm1vHmoWmtaJCFZ85xeYJ8bZQioFFYoRLN0eqWhpkgr8JzKj0VPoqKHrow==} + hasBin: true + dependencies: + chalk: 4.1.0 + enquirer: 2.3.6 + fs-extra: 9.1.0 + jsonc-parser: 3.0.0 + nx: 13.2.0 + rxjs: 6.6.7 + rxjs-for-await: 0.0.2_rxjs@6.6.7 + semver: 7.3.4 + tmp: 0.2.1 + tslib: 2.3.1 + yargs-parser: 20.0.0 + dev: true + /@nrwl/web/13.1.4_42cab1dece2b2240094de84cfd414406: resolution: {integrity: sha512-ana2YrMHYltowOHG3f3+EzA5jQVL4+QsVniN8qXFJrwecpFEoRlCZYGvhomhe3TyC4QlQkmmSm575foV8dzAyQ==} dependencies: @@ -4581,18 +4665,18 @@ packages: - utf-8-validate dev: true - /@nrwl/workspace/13.1.4_wp-prettier@2.2.1-beta-1: - resolution: {integrity: sha512-dQlxswf2XlMyEJBK4+fZHQTpxtevcWzDCVO9iLjuvL1XZDbDQG95+N7DsASq67qOxFAlpYWVxAVZAXIHsnX9tQ==} + /@nrwl/workspace/13.2.0_wp-prettier@2.2.1-beta-1: + resolution: {integrity: sha512-k0iUdJRsn8JY4SID/ybYDednKEWtYOgsapgShBzUjv2BZJoRfXuPCp21VuH6kdZDZx18/i5uOpmjzju1U12PkQ==} peerDependencies: prettier: ^2.3.0 peerDependenciesMeta: prettier: optional: true dependencies: - '@nrwl/cli': 13.1.4 - '@nrwl/devkit': 13.1.4 - '@nrwl/jest': 13.1.4 - '@nrwl/linter': 13.1.4 + '@nrwl/cli': 13.2.0 + '@nrwl/devkit': 13.2.0 + '@nrwl/jest': 13.2.0 + '@nrwl/linter': 13.2.0 '@parcel/watcher': 2.0.0-alpha.11 chalk: 4.1.0 chokidar: 3.5.2 @@ -16109,14 +16193,21 @@ packages: resolution: {integrity: sha512-LpCfZCWsVEtmD2SI1j2KRaw1uIyn4DJ3eRzsjnDYitbq38aORpkvYO+L0zVMZRNDSYSRGTsuj0nHCS3OOxK/Cg==} hasBin: true dependencies: - '@nrwl/cli': 13.1.4 + '@nrwl/cli': 13.2.0 dev: true /nx/13.1.4: resolution: {integrity: sha512-m2j3wymaFlEl/7EoGxlgRzdmgQV1Rsh42df1cM8xFzAzV8ZGtR3Zq19qK7r9SUabpq8jMzp1e6rLQTHewCJWig==} hasBin: true dependencies: - '@nrwl/cli': 13.1.4 + '@nrwl/cli': 13.2.0 + dev: true + + /nx/13.2.0: + resolution: {integrity: sha512-6EmHNwU3wsxfpy57+MQGbMcvrd9CImw3OU2OCW8Ddywi6UVmoVevaHgcxyOsUlaicj/4xU+7Pv5fGt4q1uAjyw==} + hasBin: true + dependencies: + '@nrwl/cli': 13.2.0 dev: true /oauth-1.0a/2.2.6: From a21af3d3c66eb84a6a113e9f1e741c5e7fd55f2e Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Fri, 26 Nov 2021 15:14:49 -0400 Subject: [PATCH 05/15] add resolvePackage(), resolvePackagePath() utilities --- packages/js/e2e-environment/README.md | 4 +- .../js/e2e-environment/bin/docker-compose.js | 5 +- .../bin/e2e-test-integration.js | 15 ++-- .../e2e-environment/utils/get-plugin-zip.js | 5 +- packages/js/e2e-environment/utils/index.js | 6 +- .../js/e2e-environment/utils/test-config.js | 71 +++++++++++++++++-- 6 files changed, 81 insertions(+), 25 deletions(-) diff --git a/packages/js/e2e-environment/README.md b/packages/js/e2e-environment/README.md index 26752959fba..f81679bcf82 100644 --- a/packages/js/e2e-environment/README.md +++ b/packages/js/e2e-environment/README.md @@ -60,10 +60,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; diff --git a/packages/js/e2e-environment/bin/docker-compose.js b/packages/js/e2e-environment/bin/docker-compose.js index b6046773bae..9bfd509ba76 100755 --- a/packages/js/e2e-environment/bin/docker-compose.js +++ b/packages/js/e2e-environment/bin/docker-compose.js @@ -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', diff --git a/packages/js/e2e-environment/bin/e2e-test-integration.js b/packages/js/e2e-environment/bin/e2e-test-integration.js index 09f0c95dcf5..be837564888 100755 --- a/packages/js/e2e-environment/bin/e2e-test-integration.js +++ b/packages/js/e2e-environment/bin/e2e-test-integration.js @@ -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 ) { diff --git a/packages/js/e2e-environment/utils/get-plugin-zip.js b/packages/js/e2e-environment/utils/get-plugin-zip.js index a537813e408..91be84837c1 100644 --- a/packages/js/e2e-environment/utils/get-plugin-zip.js +++ b/packages/js/e2e-environment/utils/get-plugin-zip.js @@ -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 diff --git a/packages/js/e2e-environment/utils/index.js b/packages/js/e2e-environment/utils/index.js index baf54dc1b82..54e5939a5c7 100644 --- a/packages/js/e2e-environment/utils/index.js +++ b/packages/js/e2e-environment/utils/index.js @@ -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, }; diff --git a/packages/js/e2e-environment/utils/test-config.js b/packages/js/e2e-environment/utils/test-config.js index 2f4f187e5c3..29f426c835f 100644 --- a/packages/js/e2e-environment/utils/test-config.js +++ b/packages/js/e2e-environment/utils/test-config.js @@ -19,15 +19,72 @@ 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 { + resolvedPackage.path = require.resolve( packageName ); + 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 packageLockData = JSON.parse( packageLockContent ); + + for ( const [ key, value ] of Object.entries( packageLockData.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 package = resolvePackage( packageName ); + packagePath = package.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 +151,6 @@ module.exports = { getTestConfig, getAdminConfig, resolveLocalE2ePath, + resolvePackage, + resolvePackagePath, }; From 25cd08933c3f797c056081e7874927efd473913a Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Fri, 26 Nov 2021 16:55:22 -0400 Subject: [PATCH 06/15] add --ext and --format arguments --- packages/js/e2e-core-tests/index.js | 15 +-- packages/js/e2e-environment/bin/scaffold.js | 140 +++++++++++--------- 2 files changed, 83 insertions(+), 72 deletions(-) diff --git a/packages/js/e2e-core-tests/index.js b/packages/js/e2e-core-tests/index.js index 949cffc0690..265ecaf6522 100644 --- a/packages/js/e2e-core-tests/index.js +++ b/packages/js/e2e-core-tests/index.js @@ -5,18 +5,15 @@ const allSpecs = require( './specs' ); const fs = require( 'fs' ); /** - * Read test set up configuration for the test scaffolding tool. + * Test set up configuration for the test scaffolding tool. */ -const getObjectFromJsonFile = ( filename ) => { - const specs = fs.readFileSync( filename ); - return JSON.parse( specs ); +const packageInstallFiles = { + testSpecs: 'data/install-specs.json', + defaultJson: 'data/default-test-config.json', + initializeSh: 'data/initialize.sh.default', }; -const getTestInstallSpecs = () => getObjectFromJsonFile( './data/install-specs.json' ); -const getSampleDefaultJson = () => getObjectFromJsonFile( './data/default-test-config.json' ); - module.exports = { allSpecs, - getTestInstallSpecs, - getSampleDefaultJson, + packageInstallFiles, }; diff --git a/packages/js/e2e-environment/bin/scaffold.js b/packages/js/e2e-environment/bin/scaffold.js index 4594f6c8e76..d20187693f0 100755 --- a/packages/js/e2e-environment/bin/scaffold.js +++ b/packages/js/e2e-environment/bin/scaffold.js @@ -9,79 +9,91 @@ const sprintf = require( 'sprintf-js' ).sprintf; /** * Internal dependencies. */ -const { resolveLocalE2ePath } = require( '../utils' ); +const { + resolveLocalE2ePath, + resolvePackage, + resolvePackagePath, +} = require( '../utils' ); -const [ command, package ] = process.argv.slice( 2 ); +const args = process.argv.slice( 2 ); +const [ command, packageName ] = args; /** * Install the test scripts and sample default.json configuration */ if ( command == 'install' ) { - /** - const { getTestInstallSpecs, getSampleDefaultJson } = require( package ); - - if ( getTestInstallSpecs instanceof Function ) { - console.log( 'is a function' ); + //** + const package = resolvePackage( packageName ).name; + if ( ! package.length ) { + //@todo add error message + return; } - if ( getSampleDefaultJson instanceof Function ) { - const sampleDefaultJson = getSampleDefaultJson(); - console.log( sampleDefaultJson ); - } - /**/ - // @todo Add logic for dynamic file extension - const testExtension = 'test.js'; - const sampleDefaultJson = { - "url": "http://localhost:8084/", - "users": { - "admin": { - "username": "admin", - "password": "password" - }, - "customer": { - "username": "customer", - "password": "password" - } - }, - }; - const testSpecs = { - "active": [ - { - "name": "example", - "description": "Shopper tests", - "testFiles": [ - { - "name": "cart-begin", - "functions": [ "runCartPageTest" ] - }, { - "name": "cart-end", - "functions": [ - "testCartEnd", - "testCartEndPart2", - "testCartEndPart3", - "testCartEndPart4" - ] - } - ] - } - ] - }; + const packageSlug = package.replace( '@', '' ).replace( /\//g, '.' ); + const { packageInstallFiles } = require( package ); + const { testSpecs, defaultJson, initializeSh } = packageInstallFiles; // Write sample default.json - const packageSlug = package.replace( '@', '' ).replace( /\//g, '.' ); - const defaultJsonName = `config/default-${packageSlug}.json`; - const defaultJsonSample = resolveLocalE2ePath( defaultJsonName ); - fs.writeFileSync( defaultJsonSample, JSON.stringify( sampleDefaultJson , null, 2 ) ); - console.log( `\nWrote sample test configuration to 'tests/e2e/${defaultJsonName}'.\n` ); + if ( defaultJson ) { + const defaultJsonName = `config/default-${packageSlug}.json`; + const defaultJsonSample = resolveLocalE2ePath( defaultJsonName ); + const packageJsonSample = resolvePackagePath( defaultJson, package ); + fs.copyFileSync( packageJsonSample, defaultJsonSample ); + console.log( `\nCreated sample test configuration to 'tests/e2e/${defaultJsonName}'.\n` ); + } + + // Write sample initialize.sh + if ( initializeSh ) { + const defaultInitName = `docker/${packageSlug}.sh`; + const defaultInitPath = resolveLocalE2ePath( defaultInitName ); + const packageInitPath = resolvePackagePath( initializeSh, package ); + fs.copyFileSync( packageInitPath, defaultInitPath ); + console.log( `\nCreated sample test container initialization script to 'tests/e2e/${defaultInitName}'.\n` ); + } // Write test files - if ( testSpecs.active && testSpecs.active.length ) { + if ( ! testSpecs ) { + return; + } + + const testsSpecFile = resolvePackagePath( testSpecs, package ); + const specs = fs.readFileSync( testsSpecFile ); + const tests = JSON.parse( specs ); + const { active, deprecated } = tests; + + // 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; + } + } + + if ( active && active.length ) { const eol = "\n"; - const autoGenerate = sprintf( '/* This file was auto-generated by the command `npx wc-e2e install %s. */', package ); - const importLineFormat = sprintf( "import {%%s} from '%s';", package ); + const autoGenerate = sprintf( '/* This file was auto-generated by the command `npx wc-e2e install %s. */', packageName ); + let importLineFormat; + if ( testFormat.toLowerCase() == 'cjs' ) { + importLineFormat = sprintf( "const {%%s} = require( '%s' );", package ); + } else { + importLineFormat = sprintf( "import {%%s} from '%s';", package ); + } + // Loop through folders and files to write test scripts. - for ( let f = 0; f < testSpecs.active.length; f++ ) { - const testFolder = testSpecs.active[ f ]; - if ( ! testFolder.testFiles || ! testFolder.testFiles.length ) { + for ( let f = 0; f < active.length; f++ ) { + const testFolder = active[ f ]; + const { testFiles } = testFolder; + + if ( ! testFiles || ! testFiles.length ) { continue; } @@ -89,13 +101,13 @@ if ( command == 'install' ) { const specFolderPath = resolveLocalE2ePath( specFolder ); // Create the test folder if it doesn't exist. - if ( ! fs.existsSync( specFolderPath) ) { + if ( ! fs.existsSync( specFolderPath ) ) { fs.mkdirSync( specFolderPath ); } // Create the test files. - for ( let t = 0; t < testFolder.testFiles.length; t++ ) { - const testFile = testFolder.testFiles[ t ]; + for ( let t = 0; t < testFiles.length; t++ ) { + const testFile = testFiles[ t ]; if ( ! testFile.functions.length ) { continue; } @@ -134,4 +146,6 @@ if ( command == 'install' ) { console.log( eol ); } } + + // @todo: deprecated files. } From 06f0908443503ed96db29190dc800de060980a81 Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Fri, 26 Nov 2021 16:56:56 -0400 Subject: [PATCH 07/15] add default container initialization --- .../e2e-core-tests/data/initialize.sh.default | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100755 packages/js/e2e-core-tests/data/initialize.sh.default diff --git a/packages/js/e2e-core-tests/data/initialize.sh.default b/packages/js/e2e-core-tests/data/initialize.sh.default new file mode 100755 index 00000000000..7c0ab8f9991 --- /dev/null +++ b/packages/js/e2e-core-tests/data/initialize.sh.default @@ -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%/ From 2ec68e0e69cea56669a4216e505ff4f52849331a Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Fri, 26 Nov 2021 17:08:27 -0400 Subject: [PATCH 08/15] code cleanup --- packages/js/e2e-environment/package.json | 4 ++-- packages/js/e2e-environment/utils/test-config.js | 8 ++++---- packages/js/e2e-utils/src/flows/with-rest-api.js | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/js/e2e-environment/package.json b/packages/js/e2e-environment/package.json index 47b12b89ba5..adc4f680866 100644 --- a/packages/js/e2e-environment/package.json +++ b/packages/js/e2e-environment/package.json @@ -32,9 +32,9 @@ "jest": "^25.1.0", "jest-each": "25.5.0", "jest-puppeteer": "^4.4.0", - "node-stream-zip": "^1.13.6", + "node-stream-zip": "^1.13.6", "request": "^2.88.2", - "sprintf-js": "^1.1.2" + "sprintf-js": "^1.1.2" }, "devDependencies": { "@babel/cli": "7.12.8", diff --git a/packages/js/e2e-environment/utils/test-config.js b/packages/js/e2e-environment/utils/test-config.js index 29f426c835f..430bbc6da59 100644 --- a/packages/js/e2e-environment/utils/test-config.js +++ b/packages/js/e2e-environment/utils/test-config.js @@ -44,9 +44,9 @@ const resolvePackage = ( packageName, allowRecurse = true ) => { if ( ! resolvedPackage.path.length && allowRecurse ) { const packageLockPath = path.resolve( appPath, 'package-lock.json' ); const packageLockContent = fs.readFileSync( packageLockPath ); - const packageLockData = JSON.parse( packageLockContent ); + const { dependencies } = JSON.parse( packageLockContent ); - for ( const [ key, value ] of Object.entries( packageLockData.dependencies ) ) { + for ( const [ key, value ] of Object.entries( dependencies ) ) { if ( value.version.indexOf( packageName ) == 0 ) { resolvedPackage = resolvePackage( key, false ); break; @@ -69,8 +69,8 @@ const resolvePackagePath = ( filename, packageName = '' ) => { if ( ! packageName.length ) { packagePath = path.resolve( __dirname, '../' ); } else { - const package = resolvePackage( packageName ); - packagePath = package.path; + const pkg = resolvePackage( packageName ); + packagePath = pkg.path; } const resolvedPath = path.resolve( diff --git a/packages/js/e2e-utils/src/flows/with-rest-api.js b/packages/js/e2e-utils/src/flows/with-rest-api.js index 5fc127ee0e5..6e6683b7e89 100644 --- a/packages/js/e2e-utils/src/flows/with-rest-api.js +++ b/packages/js/e2e-utils/src/flows/with-rest-api.js @@ -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. From 59f1f3a6b518ece5a3babc97c0b25d7e918d4833 Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Wed, 8 Dec 2021 13:47:30 -0400 Subject: [PATCH 09/15] Give installFiles its own index --- packages/js/e2e-core-tests/index.js | 14 +----- .../js/e2e-core-tests/installFiles/index.js | 5 ++ packages/js/e2e-environment/bin/scaffold.js | 46 +++++++++++-------- .../js/e2e-environment/utils/test-config.js | 14 +++++- 4 files changed, 46 insertions(+), 33 deletions(-) create mode 100644 packages/js/e2e-core-tests/installFiles/index.js diff --git a/packages/js/e2e-core-tests/index.js b/packages/js/e2e-core-tests/index.js index 265ecaf6522..e30dc29012b 100644 --- a/packages/js/e2e-core-tests/index.js +++ b/packages/js/e2e-core-tests/index.js @@ -4,16 +4,4 @@ const allSpecs = require( './specs' ); const fs = require( 'fs' ); -/** - * Test set up configuration for the test scaffolding tool. - */ -const packageInstallFiles = { - testSpecs: 'data/install-specs.json', - defaultJson: 'data/default-test-config.json', - initializeSh: 'data/initialize.sh.default', -}; - -module.exports = { - allSpecs, - packageInstallFiles, -}; +module.exports = allSpecs; diff --git a/packages/js/e2e-core-tests/installFiles/index.js b/packages/js/e2e-core-tests/installFiles/index.js new file mode 100644 index 00000000000..202b83fcfda --- /dev/null +++ b/packages/js/e2e-core-tests/installFiles/index.js @@ -0,0 +1,5 @@ +module.exports = { + testSpecs: 'data/scaffold-tests.json', + defaultJson: 'data/default-test-config.json', + initializeSh: 'data/initialize.sh.default', +}; diff --git a/packages/js/e2e-environment/bin/scaffold.js b/packages/js/e2e-environment/bin/scaffold.js index d20187693f0..ac74a756206 100755 --- a/packages/js/e2e-environment/bin/scaffold.js +++ b/packages/js/e2e-environment/bin/scaffold.js @@ -22,32 +22,31 @@ const [ command, packageName ] = args; * Install the test scripts and sample default.json configuration */ if ( command == 'install' ) { - //** - const package = resolvePackage( packageName ).name; - if ( ! package.length ) { + // `package` is a reserved word + const pkg = resolvePackage( packageName ).name; + if ( ! pkg.length ) { //@todo add error message return; } - const packageSlug = package.replace( '@', '' ).replace( /\//g, '.' ); - const { packageInstallFiles } = require( package ); - const { testSpecs, defaultJson, initializeSh } = packageInstallFiles; + const packageSlug = pkg.replace( '@', '' ).replace( /\//g, '.' ); + const { testSpecs, defaultJson, initializeSh } = require( `${pkg}/installFiles` ); // Write sample default.json if ( defaultJson ) { const defaultJsonName = `config/default-${packageSlug}.json`; const defaultJsonSample = resolveLocalE2ePath( defaultJsonName ); - const packageJsonSample = resolvePackagePath( defaultJson, package ); + const packageJsonSample = resolvePackagePath( defaultJson, pkg ); fs.copyFileSync( packageJsonSample, defaultJsonSample ); - console.log( `\nCreated sample test configuration to 'tests/e2e/${defaultJsonName}'.\n` ); + console.log( `Created sample test configuration to 'tests/e2e/${defaultJsonName}'.` ); } // Write sample initialize.sh if ( initializeSh ) { const defaultInitName = `docker/${packageSlug}.sh`; const defaultInitPath = resolveLocalE2ePath( defaultInitName ); - const packageInitPath = resolvePackagePath( initializeSh, package ); + const packageInitPath = resolvePackagePath( initializeSh, pkg ); fs.copyFileSync( packageInitPath, defaultInitPath ); - console.log( `\nCreated sample test container initialization script to 'tests/e2e/${defaultInitName}'.\n` ); + console.log( `Created sample test container initialization script to 'tests/e2e/${defaultInitName}'.` ); } // Write test files @@ -55,7 +54,7 @@ if ( command == 'install' ) { return; } - const testsSpecFile = resolvePackagePath( testSpecs, package ); + const testsSpecFile = resolvePackagePath( testSpecs, pkg ); const specs = fs.readFileSync( testsSpecFile ); const tests = JSON.parse( specs ); const { active, deprecated } = tests; @@ -79,15 +78,26 @@ if ( command == 'install' ) { } 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; if ( testFormat.toLowerCase() == 'cjs' ) { - importLineFormat = sprintf( "const {%%s} = require( '%s' );", package ); + importLineFormat = sprintf( "const {%%s} = require( '%s' );", pkg ); } else { - importLineFormat = sprintf( "import {%%s} from '%s';", package ); + importLineFormat = sprintf( "import {%%s} from '%s';", pkg ); } + // Create the entire folder structure if not present + let specFolderPath; + const rootFolders = [ '../../tests', '../e2e', 'specs' ]; + rootFolders.forEach( ( folder ) => { + specFolderPath = resolveLocalE2ePath( folder ); + if ( ! fs.existsSync( specFolderPath ) ) { + fs.mkdirSync( specFolderPath ); + } + } ); + // Loop through folders and files to write test scripts. for ( let f = 0; f < active.length; f++ ) { const testFolder = active[ f ]; @@ -98,7 +108,7 @@ if ( command == 'install' ) { } const specFolder = testFolder.name.length ? `specs/${testFolder.name}` : 'specs'; - const specFolderPath = resolveLocalE2ePath( specFolder ); + specFolderPath = resolveLocalE2ePath( specFolder ); // Create the test folder if it doesn't exist. if ( ! fs.existsSync( specFolderPath ) ) { @@ -131,19 +141,17 @@ if ( command == 'install' ) { importPrefix = eol; } else { testSeparator = ', '; - testTerminator = ''; + testTerminator = ' '; importPrefix = ' '; } const testImport = testFile.functions.join( testSeparator ) + testTerminator; - // '' adds a blank line - buffer.push( sprintf( importLineFormat, importPrefix + testImport ), '' ); + 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 ); + buffer.push( ...functionCalls, blankLine ); fs.writeFileSync( testFilePath, buffer.join( eol ) ); } - console.log( eol ); } } diff --git a/packages/js/e2e-environment/utils/test-config.js b/packages/js/e2e-environment/utils/test-config.js index 430bbc6da59..35d369cf182 100644 --- a/packages/js/e2e-environment/utils/test-config.js +++ b/packages/js/e2e-environment/utils/test-config.js @@ -32,7 +32,19 @@ const resolvePackage = ( packageName, allowRecurse = true ) => { const resolvedPackage = {}; try { - resolvedPackage.path = require.resolve( packageName ); + 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. From 2698cb36fffd3e7f248a95ab64a63d587dc99e05 Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Wed, 8 Dec 2021 15:12:27 -0400 Subject: [PATCH 10/15] add overwrite confirmation --- packages/js/e2e-core-tests/index.js | 1 - packages/js/e2e-environment/bin/scaffold.js | 41 ++++++++++++++++++--- packages/js/e2e-environment/package.json | 1 + pnpm-lock.yaml | 7 ++++ 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/packages/js/e2e-core-tests/index.js b/packages/js/e2e-core-tests/index.js index e30dc29012b..9fd036edd21 100644 --- a/packages/js/e2e-core-tests/index.js +++ b/packages/js/e2e-core-tests/index.js @@ -2,6 +2,5 @@ * Internal dependencies */ const allSpecs = require( './specs' ); -const fs = require( 'fs' ); module.exports = allSpecs; diff --git a/packages/js/e2e-environment/bin/scaffold.js b/packages/js/e2e-environment/bin/scaffold.js index ac74a756206..49d252f425e 100755 --- a/packages/js/e2e-environment/bin/scaffold.js +++ b/packages/js/e2e-environment/bin/scaffold.js @@ -5,6 +5,7 @@ */ const fs = require( 'fs' ); const sprintf = require( 'sprintf-js' ).sprintf; +const readlineSync = require( 'readline-sync' ); /** * Internal dependencies. @@ -18,6 +19,18 @@ const { const args = process.argv.slice( 2 ); const [ command, packageName ] = args; +/** + * 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; +}; + /** * Install the test scripts and sample default.json configuration */ @@ -49,11 +62,11 @@ if ( command == 'install' ) { console.log( `Created sample test container initialization script to 'tests/e2e/${defaultInitName}'.` ); } - // Write test files if ( ! testSpecs ) { return; } + // Write test files const testsSpecFile = resolvePackagePath( testSpecs, pkg ); const specs = fs.readFileSync( testsSpecFile ); const tests = JSON.parse( specs ); @@ -82,6 +95,8 @@ if ( command == 'install' ) { 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 { @@ -100,6 +115,11 @@ if ( command == 'install' ) { // 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; @@ -125,10 +145,21 @@ if ( command == 'install' ) { const testFileName = `${specFolder}/${testFile.name}.${testExtension}`; const testFilePath = resolveLocalE2ePath( testFileName ); - // @todo Add check to see if file exists - // fs.existsSync - // @todo Confirm overwrite - // readline + // 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/' + testFileName ); let buffer = [ autoGenerate ]; diff --git a/packages/js/e2e-environment/package.json b/packages/js/e2e-environment/package.json index adc4f680866..c7005cb16c6 100644 --- a/packages/js/e2e-environment/package.json +++ b/packages/js/e2e-environment/package.json @@ -33,6 +33,7 @@ "jest-each": "25.5.0", "jest-puppeteer": "^4.4.0", "node-stream-zip": "^1.13.6", + "readline-sync": "^1.4.10", "request": "^2.88.2", "sprintf-js": "^1.1.2" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7d9a5c0fb9b..9a39c20dbf2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -129,6 +129,7 @@ importers: jest-puppeteer: ^4.4.0 ndb: ^1.1.5 node-stream-zip: ^1.13.6 + readline-sync: ^1.4.10 request: ^2.88.2 semver: ^7.3.2 sprintf-js: ^1.1.2 @@ -145,6 +146,7 @@ importers: jest-each: 25.5.0 jest-puppeteer: 4.4.0 node-stream-zip: 1.15.0 + readline-sync: 1.4.10 request: 2.88.2 sprintf-js: 1.1.2 devDependencies: @@ -18063,6 +18065,11 @@ packages: dependencies: picomatch: 2.3.0 + /readline-sync/1.4.10: + resolution: {integrity: sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==} + engines: {node: '>= 0.8.0'} + dev: false + /realpath-native/1.1.0: resolution: {integrity: sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==} engines: {node: '>=4'} From 036ab6a464b4b3f3a61d3a4c3e13f7c23355db80 Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Wed, 8 Dec 2021 16:56:53 -0400 Subject: [PATCH 11/15] simplify core test scaffold, add default setup --- .../default-test-config.json | 0 .../js/e2e-core-tests/installFiles/index.js | 6 +- .../initialize.sh.default | 0 .../scaffold-tests.json | 0 packages/js/e2e-environment/bin/scaffold.js | 68 ++++----- .../js/e2e-environment/config/default.json | 135 +----------------- 6 files changed, 33 insertions(+), 176 deletions(-) rename packages/js/e2e-core-tests/{data => installFiles}/default-test-config.json (100%) rename packages/js/e2e-core-tests/{data => installFiles}/initialize.sh.default (100%) rename packages/js/e2e-core-tests/{data => installFiles}/scaffold-tests.json (100%) diff --git a/packages/js/e2e-core-tests/data/default-test-config.json b/packages/js/e2e-core-tests/installFiles/default-test-config.json similarity index 100% rename from packages/js/e2e-core-tests/data/default-test-config.json rename to packages/js/e2e-core-tests/installFiles/default-test-config.json diff --git a/packages/js/e2e-core-tests/installFiles/index.js b/packages/js/e2e-core-tests/installFiles/index.js index 202b83fcfda..6601661582c 100644 --- a/packages/js/e2e-core-tests/installFiles/index.js +++ b/packages/js/e2e-core-tests/installFiles/index.js @@ -1,5 +1,5 @@ module.exports = { - testSpecs: 'data/scaffold-tests.json', - defaultJson: 'data/default-test-config.json', - initializeSh: 'data/initialize.sh.default', + testSpecs: 'installFiles/scaffold-tests.json', + defaultJson: 'installFiles/default-test-config.json', + initializeSh: 'installFiles/initialize.sh.default', }; diff --git a/packages/js/e2e-core-tests/data/initialize.sh.default b/packages/js/e2e-core-tests/installFiles/initialize.sh.default similarity index 100% rename from packages/js/e2e-core-tests/data/initialize.sh.default rename to packages/js/e2e-core-tests/installFiles/initialize.sh.default diff --git a/packages/js/e2e-core-tests/data/scaffold-tests.json b/packages/js/e2e-core-tests/installFiles/scaffold-tests.json similarity index 100% rename from packages/js/e2e-core-tests/data/scaffold-tests.json rename to packages/js/e2e-core-tests/installFiles/scaffold-tests.json diff --git a/packages/js/e2e-environment/bin/scaffold.js b/packages/js/e2e-environment/bin/scaffold.js index 49d252f425e..fa37df08767 100755 --- a/packages/js/e2e-environment/bin/scaffold.js +++ b/packages/js/e2e-environment/bin/scaffold.js @@ -5,7 +5,6 @@ */ const fs = require( 'fs' ); const sprintf = require( 'sprintf-js' ).sprintf; -const readlineSync = require( 'readline-sync' ); /** * Internal dependencies. @@ -15,26 +14,26 @@ const { resolvePackage, resolvePackagePath, } = require( '../utils' ); +const { + createLocalE2ePath, + confirm, + confirmLocalCopy, + installDefaults +} = require( '../utils/scaffold' ); const args = process.argv.slice( 2 ); const [ command, packageName ] = args; -/** - * 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; -}; - /** * 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 ) { @@ -47,19 +46,17 @@ if ( command == 'install' ) { // Write sample default.json if ( defaultJson ) { const defaultJsonName = `config/default-${packageSlug}.json`; - const defaultJsonSample = resolveLocalE2ePath( defaultJsonName ); - const packageJsonSample = resolvePackagePath( defaultJson, pkg ); - fs.copyFileSync( packageJsonSample, defaultJsonSample ); - console.log( `Created sample test configuration to 'tests/e2e/${defaultJsonName}'.` ); + if ( confirmLocalCopy( defaultJsonName, defaultJson, pkg ) ) { + console.log( `Created sample test configuration to 'tests/e2e/${defaultJsonName}'.` ); + } } // Write sample initialize.sh if ( initializeSh ) { const defaultInitName = `docker/${packageSlug}.sh`; - const defaultInitPath = resolveLocalE2ePath( defaultInitName ); - const packageInitPath = resolvePackagePath( initializeSh, pkg ); - fs.copyFileSync( packageInitPath, defaultInitPath ); - console.log( `Created sample test container initialization script to 'tests/e2e/${defaultInitName}'.` ); + if ( confirmLocalCopy( defaultInitName, initializeSh, pkg ) ) { + console.log( `Created sample test container initialization script to 'tests/e2e/${defaultInitName}'.` ); + } } if ( ! testSpecs ) { @@ -103,15 +100,8 @@ if ( command == 'install' ) { importLineFormat = sprintf( "import {%%s} from '%s';", pkg ); } - // Create the entire folder structure if not present - let specFolderPath; - const rootFolders = [ '../../tests', '../e2e', 'specs' ]; - rootFolders.forEach( ( folder ) => { - specFolderPath = resolveLocalE2ePath( folder ); - if ( ! fs.existsSync( specFolderPath ) ) { - fs.mkdirSync( specFolderPath ); - } - } ); + // 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++ ) { @@ -127,12 +117,11 @@ if ( command == 'install' ) { continue; } - const specFolder = testFolder.name.length ? `specs/${testFolder.name}` : 'specs'; - specFolderPath = resolveLocalE2ePath( specFolder ); - - // Create the test folder if it doesn't exist. - if ( ! fs.existsSync( specFolderPath ) ) { - fs.mkdirSync( specFolderPath ); + let specFolder; + if ( testFolder.name.length ) { + specFolder = createLocalE2ePath( testFolder.name ); + } else { + specFolder = specFolderPath; } // Create the test files. @@ -142,8 +131,8 @@ if ( command == 'install' ) { continue; } - const testFileName = `${specFolder}/${testFile.name}.${testExtension}`; - const testFilePath = resolveLocalE2ePath( testFileName ); + const testFileName = `${testFolder.name}/${testFile.name}.${testExtension}`; + const testFilePath = `${specFolder}/${testFile.name}.${testExtension}`; // Check to see if file exists. if ( fs.existsSync( testFilePath ) ) { @@ -161,7 +150,7 @@ if ( command == 'install' ) { } } - console.log( 'Writing tests/e2e/' + testFileName ); + console.log( 'Writing tests/e2e/specs/' + testFileName ); let buffer = [ autoGenerate ]; let testSeparator, testTerminator, importPrefix; @@ -185,6 +174,5 @@ if ( command == 'install' ) { } } } - // @todo: deprecated files. } diff --git a/packages/js/e2e-environment/config/default.json b/packages/js/e2e-environment/config/default.json index e70b3c6c5d8..e01ee4e9a27 100644 --- a/packages/js/e2e-environment/config/default.json +++ b/packages/js/e2e-environment/config/default.json @@ -1,5 +1,6 @@ { "url": "http://localhost:8084/", + "appName": "woocommerce_e2e", "users": { "admin": { "username": "admin", @@ -15,133 +16,12 @@ "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" + "name": "Variable Product with Three Variations" } }, "addresses": { "admin": { "store": { - "email": "admin@woocommercecoree2etestsuite.com", "firstname": "John", "lastname": "Doe", "company": "Automattic", @@ -181,17 +61,6 @@ } } }, - "orders": { - "basicPaidOrder": { - "paymentMethod": "cod", - "status": "processing", - "billing": { - "firstName": "John", - "lastName": "Doe", - "email": "john.doe@example.com" - } - } - }, "onboardingwizard": { "industry": "Test industry", "numberofproducts": "1 - 10", From 3041bb25faaf79248f740a5fa38975df2af1a102 Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Wed, 8 Dec 2021 19:08:29 -0400 Subject: [PATCH 12/15] add uninstall feature --- packages/js/e2e-environment/bin/scaffold.js | 99 +++++++++++---- packages/js/e2e-environment/bin/wc-e2e.sh | 3 +- .../installFiles/initialize.sh | 25 ++++ .../installFiles/jest.config.js | 8 ++ .../installFiles/jest.setup.js | 80 ++++++++++++ packages/js/e2e-environment/utils/scaffold.js | 119 ++++++++++++++++++ 6 files changed, 311 insertions(+), 23 deletions(-) create mode 100755 packages/js/e2e-environment/installFiles/initialize.sh create mode 100644 packages/js/e2e-environment/installFiles/jest.config.js create mode 100644 packages/js/e2e-environment/installFiles/jest.setup.js create mode 100644 packages/js/e2e-environment/utils/scaffold.js diff --git a/packages/js/e2e-environment/bin/scaffold.js b/packages/js/e2e-environment/bin/scaffold.js index fa37df08767..83f7c6ecabc 100755 --- a/packages/js/e2e-environment/bin/scaffold.js +++ b/packages/js/e2e-environment/bin/scaffold.js @@ -18,12 +18,32 @@ 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 */ @@ -40,12 +60,12 @@ if ( command == 'install' ) { //@todo add error message return; } - const packageSlug = pkg.replace( '@', '' ).replace( /\//g, '.' ); - const { testSpecs, defaultJson, initializeSh } = require( `${pkg}/installFiles` ); + const { packageSlug, testSpecs, defaultJson, initializeSh } = getPackageData( pkg ); // Write sample default.json if ( defaultJson ) { const defaultJsonName = `config/default-${packageSlug}.json`; + createLocalE2ePath( 'config' ); if ( confirmLocalCopy( defaultJsonName, defaultJson, pkg ) ) { console.log( `Created sample test configuration to 'tests/e2e/${defaultJsonName}'.` ); } @@ -54,6 +74,7 @@ if ( command == 'install' ) { // Write sample initialize.sh if ( initializeSh ) { const defaultInitName = `docker/${packageSlug}.sh`; + createLocalE2ePath( 'docker' ); if ( confirmLocalCopy( defaultInitName, initializeSh, pkg ) ) { console.log( `Created sample test container initialization script to 'tests/e2e/${defaultInitName}'.` ); } @@ -69,24 +90,6 @@ if ( command == 'install' ) { const tests = JSON.parse( specs ); const { active, deprecated } = tests; - // 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; - } - } - if ( active && active.length ) { const blankLine = ''; const eol = "\n"; @@ -119,7 +122,7 @@ if ( command == 'install' ) { let specFolder; if ( testFolder.name.length ) { - specFolder = createLocalE2ePath( testFolder.name ); + specFolder = createLocalE2ePath( `specs/${testFolder.name}` ); } else { specFolder = specFolderPath; } @@ -137,7 +140,7 @@ if ( command == 'install' ) { // 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: `; + confirmPrompt = `${testFileName} already exists. Overwrite? [y]es/[n]o/[a]ll/[q]uit: `; overwriteFiles = confirm( confirmPrompt, 'anqy' ); overwriteFiles = overwriteFiles.toLowerCase(); } @@ -175,4 +178,56 @@ if ( command == 'install' ) { } } // @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/default-${packageSlug}.json`; + confirmLocalDelete( defaultJsonName ); + } + + // Delete sample initialize.sh + if ( initializeSh ) { + const defaultInitName = `docker/${packageSlug}.sh`; + confirmLocalDelete( defaultInitName ); + } + + if ( ! testSpecs ) { + return; + } + + const testsSpecFile = resolvePackagePath( testSpecs, pkg ); + const specs = fs.readFileSync( testsSpecFile ); + const tests = JSON.parse( specs ); + const { active, deprecated } = 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/${testFolder.name}` : 'specs'; + for ( let t = 0; t < testFiles.length; t++ ) { + const testFile = testFiles[ t ]; + const testFilePath = `${specFolder}/${testFile.name}.${testExtension}`; + + confirmLocalDelete( testFilePath ); + } + } + } diff --git a/packages/js/e2e-environment/bin/wc-e2e.sh b/packages/js/e2e-environment/bin/wc-e2e.sh index bbba17c2802..9f7fa6d72d1 100755 --- a/packages/js/e2e-environment/bin/wc-e2e.sh +++ b/packages/js/e2e-environment/bin/wc-e2e.sh @@ -71,7 +71,8 @@ case $1 in ./bin/wait-for-build.sh && ./bin/e2e-test-integration.js --dev --debug $2 TESTRESULT=$? ;; - 'install') + 'install' | \ + 'uninstall') ./bin/scaffold.js $@ ;; *) diff --git a/packages/js/e2e-environment/installFiles/initialize.sh b/packages/js/e2e-environment/installFiles/initialize.sh new file mode 100755 index 00000000000..7c0ab8f9991 --- /dev/null +++ b/packages/js/e2e-environment/installFiles/initialize.sh @@ -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%/ diff --git a/packages/js/e2e-environment/installFiles/jest.config.js b/packages/js/e2e-environment/installFiles/jest.config.js new file mode 100644 index 00000000000..4e3b87bcce7 --- /dev/null +++ b/packages/js/e2e-environment/installFiles/jest.config.js @@ -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; diff --git a/packages/js/e2e-environment/installFiles/jest.setup.js b/packages/js/e2e-environment/installFiles/jest.setup.js new file mode 100644 index 00000000000..5fd474f804f --- /dev/null +++ b/packages/js/e2e-environment/installFiles/jest.setup.js @@ -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'); +}); diff --git a/packages/js/e2e-environment/utils/scaffold.js b/packages/js/e2e-environment/utils/scaffold.js new file mode 100644 index 00000000000..c8ffb6151a6 --- /dev/null +++ b/packages/js/e2e-environment/utils/scaffold.js @@ -0,0 +1,119 @@ +/** + * External dependencies. + */ +const fs = require( 'fs' ); +const readlineSync = require( 'readline-sync' ); +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 = [ '../../tests', '../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 ); + const confirmPrompt = `${localE2ePath} exists. Delete? [y]es/[n]o: `; + + if ( ! fs.existsSync( localPath ) ) { + return; + } + 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}/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/initialize.sh', 'installFiles/initialize.sh' ); + + createLocalE2ePath( 'config' ); + console.log( 'Writing tests/e2e/config/jest.config.js' ); + confirmLocalCopy( 'config/jest.config.js', 'installFiles/jest.config.js' ); + console.log( 'Writing tests/e2e/config/jest.setup.js' ); + confirmLocalCopy( 'config/jest.setup.js', 'installFiles/jest.setup.js' ); +}; + +module.exports = { + createLocalE2ePath, + confirm, + confirmLocalCopy, + confirmLocalDelete, + getPackageData, + installDefaults, +}; From 2c5bed66e7c0191c15a60f6df20497567a4898e9 Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Wed, 8 Dec 2021 19:38:45 -0400 Subject: [PATCH 13/15] use path.sep throughout scaffolding --- packages/js/e2e-environment/bin/scaffold.js | 22 +++++++++---------- packages/js/e2e-environment/utils/scaffold.js | 19 ++++++++++------ 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/packages/js/e2e-environment/bin/scaffold.js b/packages/js/e2e-environment/bin/scaffold.js index 83f7c6ecabc..e7a9bd0bc95 100755 --- a/packages/js/e2e-environment/bin/scaffold.js +++ b/packages/js/e2e-environment/bin/scaffold.js @@ -4,13 +4,13 @@ * External dependencies. */ const fs = require( 'fs' ); +const path = require( 'path' ); const sprintf = require( 'sprintf-js' ).sprintf; /** * Internal dependencies. */ const { - resolveLocalE2ePath, resolvePackage, resolvePackagePath, } = require( '../utils' ); @@ -64,7 +64,7 @@ if ( command == 'install' ) { // Write sample default.json if ( defaultJson ) { - const defaultJsonName = `config/default-${packageSlug}.json`; + 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}'.` ); @@ -73,7 +73,7 @@ if ( command == 'install' ) { // Write sample initialize.sh if ( initializeSh ) { - const defaultInitName = `docker/${packageSlug}.sh`; + 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}'.` ); @@ -122,7 +122,7 @@ if ( command == 'install' ) { let specFolder; if ( testFolder.name.length ) { - specFolder = createLocalE2ePath( `specs/${testFolder.name}` ); + specFolder = createLocalE2ePath( `specs${path.sep}${testFolder.name}` ); } else { specFolder = specFolderPath; } @@ -134,8 +134,8 @@ if ( command == 'install' ) { continue; } - const testFileName = `${testFolder.name}/${testFile.name}.${testExtension}`; - const testFilePath = `${specFolder}/${testFile.name}.${testExtension}`; + 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 ) ) { @@ -189,13 +189,13 @@ if ( command == 'install' ) { // Delete sample default.json if ( defaultJson ) { - const defaultJsonName = `config/default-${packageSlug}.json`; + const defaultJsonName = `config${path.sep}default-${packageSlug}.json`; confirmLocalDelete( defaultJsonName ); } // Delete sample initialize.sh if ( initializeSh ) { - const defaultInitName = `docker/${packageSlug}.sh`; + const defaultInitName = `docker${path.sep}${packageSlug}.sh`; confirmLocalDelete( defaultInitName ); } @@ -206,7 +206,7 @@ if ( command == 'install' ) { const testsSpecFile = resolvePackagePath( testSpecs, pkg ); const specs = fs.readFileSync( testsSpecFile ); const tests = JSON.parse( specs ); - const { active, deprecated } = tests; + const { active } = tests; if ( ! active || ! active.length ) { return; @@ -221,10 +221,10 @@ if ( command == 'install' ) { continue; } - const specFolder = testFolder.name.length ? `specs/${testFolder.name}` : 'specs'; + 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}/${testFile.name}.${testExtension}`; + const testFilePath = `${specFolder}${path.sep}${testFile.name}.${testExtension}`; confirmLocalDelete( testFilePath ); } diff --git a/packages/js/e2e-environment/utils/scaffold.js b/packages/js/e2e-environment/utils/scaffold.js index c8ffb6151a6..7662a35ad50 100644 --- a/packages/js/e2e-environment/utils/scaffold.js +++ b/packages/js/e2e-environment/utils/scaffold.js @@ -2,7 +2,12 @@ * External dependencies. */ const fs = require( 'fs' ); +const path = require( 'path' ); const readlineSync = require( 'readline-sync' ); + +/** + * Internal dependencies. + */ const { resolveLocalE2ePath, resolvePackagePath } = require( './test-config' ); /** @@ -12,7 +17,7 @@ const { resolveLocalE2ePath, resolvePackagePath } = require( './test-config' ); */ const createLocalE2ePath = ( relativePath ) => { let specFolderPath = ''; - const folders = [ '../../tests', '../e2e', relativePath ]; + const folders = [ `..${path.sep}..${path.sep}tests`, `..${path.sep}e2e`, relativePath ]; folders.forEach( ( folder ) => { specFolderPath = resolveLocalE2ePath( folder ); if ( ! fs.existsSync( specFolderPath ) ) { @@ -70,11 +75,11 @@ const confirmLocalCopy = ( localE2ePath, packageE2ePath, packageName = '' ) => { */ const confirmLocalDelete = ( localE2ePath ) => { const localPath = resolveLocalE2ePath( localE2ePath ); - const confirmPrompt = `${localE2ePath} exists. Delete? [y]es/[n]o: `; - 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 ); @@ -89,7 +94,7 @@ const confirmLocalDelete = ( localE2ePath ) => { */ const getPackageData = ( packageName ) => { const packageSlug = packageName.replace( '@', '' ).replace( /\//g, '.' ); - const installFiles = require( `${packageName}/installFiles` ); + const installFiles = require( `${packageName}${path.sep}installFiles` ); return { packageSlug, ...installFiles }; }; @@ -100,13 +105,13 @@ const getPackageData = ( packageName ) => { const installDefaults = () => { createLocalE2ePath( 'docker' ); console.log( 'Writing tests/e2e/docker/initialize.sh' ); - confirmLocalCopy( 'docker/initialize.sh', 'installFiles/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/jest.config.js', 'installFiles/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/jest.setup.js', 'installFiles/jest.setup.js' ); + confirmLocalCopy( `config${path.sep}jest.setup.js`, `installFiles${path.sep}jest.setup.js` ); }; module.exports = { From cf66823a55013a1d17284fc722d2d1a36aed8747 Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Thu, 9 Dec 2021 16:53:06 -0400 Subject: [PATCH 14/15] add changelog and readme --- packages/js/e2e-core-tests/CHANGELOG.md | 1 + packages/js/e2e-core-tests/README.md | 12 +++ packages/js/e2e-environment/CHANGELOG.md | 2 + packages/js/e2e-environment/README.md | 13 +++ packages/js/e2e-environment/test-packages.md | 104 +++++++++++++++++++ 5 files changed, 132 insertions(+) create mode 100644 packages/js/e2e-environment/test-packages.md diff --git a/packages/js/e2e-core-tests/CHANGELOG.md b/packages/js/e2e-core-tests/CHANGELOG.md index d0a15fbe6e0..03eebaf6d20 100644 --- a/packages/js/e2e-core-tests/CHANGELOG.md +++ b/packages/js/e2e-core-tests/CHANGELOG.md @@ -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 diff --git a/packages/js/e2e-core-tests/README.md b/packages/js/e2e-core-tests/README.md index 1c48455197b..03490879b95 100644 --- a/packages/js/e2e-core-tests/README.md +++ b/packages/js/e2e-core-tests/README.md @@ -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 diff --git a/packages/js/e2e-environment/CHANGELOG.md b/packages/js/e2e-environment/CHANGELOG.md index 781c66330cb..44e84d7266a 100644 --- a/packages/js/e2e-environment/CHANGELOG.md +++ b/packages/js/e2e-environment/CHANGELOG.md @@ -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. diff --git a/packages/js/e2e-environment/README.md b/packages/js/e2e-environment/README.md index f81679bcf82..f825d5e08c6 100644 --- a/packages/js/e2e-environment/README.md +++ b/packages/js/e2e-environment/README.md @@ -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. diff --git a/packages/js/e2e-environment/test-packages.md b/packages/js/e2e-environment/test-packages.md new file mode 100644 index 00000000000..12370393d44 --- /dev/null +++ b/packages/js/e2e-environment/test-packages.md @@ -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(); +``` + From c5300e7a671a2c8df2e4c3f84420a086388ef182 Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Thu, 9 Dec 2021 17:08:48 -0400 Subject: [PATCH 15/15] undo unintended changes to default.json --- packages/js/e2e-environment/bin/scaffold.js | 2 +- .../js/e2e-environment/config/default.json | 135 +++++++++++++++++- 2 files changed, 134 insertions(+), 3 deletions(-) diff --git a/packages/js/e2e-environment/bin/scaffold.js b/packages/js/e2e-environment/bin/scaffold.js index e7a9bd0bc95..ba7a9257d8b 100755 --- a/packages/js/e2e-environment/bin/scaffold.js +++ b/packages/js/e2e-environment/bin/scaffold.js @@ -93,7 +93,7 @@ if ( command == 'install' ) { 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 ); + const autoGenerate = sprintf( '/* This file was auto-generated by the command `npx wc-e2e install %s`. */', packageName ); let importLineFormat; let overwriteFiles; let confirmPrompt; diff --git a/packages/js/e2e-environment/config/default.json b/packages/js/e2e-environment/config/default.json index e01ee4e9a27..e70b3c6c5d8 100644 --- a/packages/js/e2e-environment/config/default.json +++ b/packages/js/e2e-environment/config/default.json @@ -1,6 +1,5 @@ { "url": "http://localhost:8084/", - "appName": "woocommerce_e2e", "users": { "admin": { "username": "admin", @@ -16,12 +15,133 @@ "name": "Simple product" }, "variable": { - "name": "Variable Product with Three Variations" + "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", @@ -61,6 +181,17 @@ } } }, + "orders": { + "basicPaidOrder": { + "paymentMethod": "cod", + "status": "processing", + "billing": { + "firstName": "John", + "lastName": "Doe", + "email": "john.doe@example.com" + } + } + }, "onboardingwizard": { "industry": "Test industry", "numberofproducts": "1 - 10",