Added plugin upload functionality
This commit is contained in:
parent
7d3ac49f64
commit
268c07118e
|
@ -39,4 +39,5 @@ jobs:
|
|||
E2E_SLACK_TOKEN: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }}
|
||||
E2E_SLACK_CHANNEL: ${{ secrets.SMOKE_TEST_SLACK_CHANNEL }}
|
||||
run: |
|
||||
npx wc-e2e test:e2e ./tests/e2e/specs/smoke-tests/update-woocommerce.test.js
|
||||
npx wc-e2e test:e2e
|
||||
|
|
|
@ -52,6 +52,7 @@ tests/cli/vendor
|
|||
/tests/e2e/env/build/
|
||||
/tests/e2e/env/build-module/
|
||||
/tests/e2e/screenshots
|
||||
/tests/e2e/plugins
|
||||
/tests/e2e/utils/build/
|
||||
/tests/e2e/utils/build-module/
|
||||
|
||||
|
|
|
@ -9232,7 +9232,9 @@
|
|||
"app-root-path": "^3.0.0",
|
||||
"jest": "^25.1.0",
|
||||
"jest-each": "25.5.0",
|
||||
"jest-puppeteer": "^4.4.0"
|
||||
"jest-puppeteer": "^4.4.0",
|
||||
"node-stream-zip": "^1.13.6",
|
||||
"request": "^2.88.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@automattic/puppeteer-utils": {
|
||||
|
@ -33239,6 +33241,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node-stream-zip": {
|
||||
"version": "1.13.6",
|
||||
"resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.13.6.tgz",
|
||||
"integrity": "sha512-c7tRSVkLNOHvasWgmZ2d86cDgTWEygnkuuHNOY9c0mR3yLZtQTTrGvMaJ/fPs6+LOJn3240y30l5sjLaXFtcvw==",
|
||||
"dev": true
|
||||
},
|
||||
"nopt": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# Unreleased
|
||||
|
||||
- `updateReadyPageStatus` utility to update the status of the ready page
|
||||
- Added plugin upload functionality util that provides a method to pull a plugin zip from a remote location
|
||||
- `getRemotePluginZip( fileUrl )` to get the remote zip. Returns the filepath of the zip location.
|
||||
|
||||
# 0.2.2
|
||||
|
||||
|
|
|
@ -30,7 +30,9 @@
|
|||
"app-root-path": "^3.0.0",
|
||||
"jest": "^25.1.0",
|
||||
"jest-each": "25.5.0",
|
||||
"jest-puppeteer": "^4.4.0"
|
||||
"jest-puppeteer": "^4.4.0",
|
||||
"request": "^2.88.2",
|
||||
"node-stream-zip": "^1.13.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "7.12.0",
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
const path = require( 'path' );
|
||||
const getAppRoot = require( './app-root' );
|
||||
const fs = require('fs');
|
||||
const mkdirp = require( 'mkdirp' );
|
||||
const request = require('request');
|
||||
const StreamZip = require('node-stream-zip');
|
||||
|
||||
/**
|
||||
* Upload a plugin zip from a remote location, such as a GitHub URL or other hosted location.
|
||||
*
|
||||
* @param {string} fileUrl The URL where the zip file is located.
|
||||
* @returns {string} The path where the zip file is located.
|
||||
*/
|
||||
const getRemotePluginZip = async ( fileUrl ) => {
|
||||
const appPath = getAppRoot();
|
||||
const savePath = path.resolve( appPath, 'tests/e2e/plugins' );
|
||||
mkdirp.sync( savePath );
|
||||
|
||||
// Pull the filename from the end of the URL
|
||||
let fileName = fileUrl.split('/').pop();
|
||||
let filePath = path.join( savePath, fileName );
|
||||
|
||||
// First, download the zip file
|
||||
await downloadZip( fileUrl, filePath );
|
||||
|
||||
// Check for a nested zip and update the filepath
|
||||
filePath = await checkZip( filePath, savePath );
|
||||
|
||||
return filePath;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check the zip file for any nested zips. If one is found, extract it.
|
||||
*
|
||||
* @param {string} zipFilePath The location of the zip file.
|
||||
* @param {string} savePath The location where to save a nested zip if found.
|
||||
* @returns {string} The path where the zip file is located.
|
||||
*/
|
||||
const checkZip = async ( zipFilePath, savePath ) => {
|
||||
const zip = new StreamZip.async( { file: zipFilePath } );
|
||||
const entries = await zip.entries();
|
||||
|
||||
for (const entry of Object.values( entries )) {
|
||||
if ( entry.name.match( /.zip/ )) {
|
||||
await zip.extract( null, savePath );
|
||||
await zip.close();
|
||||
return path.join( savePath, entry.name );
|
||||
}
|
||||
}
|
||||
|
||||
return zipFilePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download the zip file from a remote location.
|
||||
*
|
||||
* @param {string} fileUrl The URL where the zip file is located.
|
||||
* @param {string} downloadPath The location where to download the zip to.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const downloadZip = async ( fileUrl, downloadPath ) => {
|
||||
const options = {
|
||||
url: fileUrl,
|
||||
method: 'GET',
|
||||
encoding: null,
|
||||
};
|
||||
|
||||
// Wrap in a promise to make the request async
|
||||
return new Promise( function( resolve, reject ) {
|
||||
request.get(options, function( err, resp, body ) {
|
||||
if ( err ) {
|
||||
reject( err );
|
||||
} else {
|
||||
resolve( body );
|
||||
}
|
||||
})
|
||||
.pipe( fs.createWriteStream( downloadPath ) );
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getRemotePluginZip,
|
||||
};
|
|
@ -1,6 +1,7 @@
|
|||
const getAppRoot = require( './app-root' );
|
||||
const { getAppName, getAppBase } = require( './app-name' );
|
||||
const { getTestConfig, getAdminConfig } = require( './test-config' );
|
||||
const { getRemotePluginZip } = require('./get-plugin-zip');
|
||||
const takeScreenshotFor = require( './take-screenshot' );
|
||||
const updateReadyPageStatus = require('./update-ready-page');
|
||||
const consoleUtils = require( './filter-console' );
|
||||
|
@ -11,6 +12,7 @@ module.exports = {
|
|||
getAppName,
|
||||
getTestConfig,
|
||||
getAdminConfig,
|
||||
getRemotePluginZip,
|
||||
takeScreenshotFor,
|
||||
updateReadyPageStatus,
|
||||
...consoleUtils,
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
const { merchant } = require( '@woocommerce/e2e-utils' );
|
||||
|
||||
const { getRemotePluginZip } = require( '@woocommerce/e2e-environment' );
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
const {
|
||||
it,
|
||||
describe,
|
||||
beforeAll,
|
||||
} = require( '@jest/globals' );
|
||||
|
||||
|
||||
const nightlyZip = 'https://github.com/woocommerce/woocommerce/releases/download/nightly/woocommerce-trunk-nightly.zip';
|
||||
const pluginName = 'WooCommerce';
|
||||
|
||||
let pluginPath;
|
||||
|
||||
describe( 'WooCommerce plugin can be uploaded and activated', () => {
|
||||
beforeAll( async () => {
|
||||
pluginPath = await getRemotePluginZip( nightlyZip );
|
||||
await merchant.login();
|
||||
});
|
||||
|
||||
afterAll( async () => {
|
||||
await merchant.logout();
|
||||
});
|
||||
|
||||
it( 'can upload and activate the WooCommerce plugin', async () => {
|
||||
await merchant.uploadAndActivatePlugin( pluginPath, pluginName );
|
||||
});
|
||||
|
||||
});
|
|
@ -8,6 +8,10 @@
|
|||
- Added new merchant flows:
|
||||
- `openWordPressUpdatesPage()`
|
||||
- `installAllUpdates()`
|
||||
- Added `getSlug()` helper to return the slug string for a provided string
|
||||
- Added `describeIf()` to conditionally run a test suite
|
||||
- Added `itIf()` to conditionally run a test case.
|
||||
- Added merchant workflows around plugins: `uploadAndActivatePlugin()`, `activatePlugin()`, `deactivatePlugin()`, `deletePlugin()`
|
||||
|
||||
# 0.1.5
|
||||
|
||||
|
|
|
@ -64,6 +64,7 @@ This package provides support for enabling retries in tests:
|
|||
- `WP_ADMIN_WC_SETTINGS` - WooCommerce settings page root
|
||||
- `WP_ADMIN_NEW_SHIPPING_ZONE` - WooCommerce new shipping zone
|
||||
- `WP_ADMIN_WC_EXTENSIONS` - WooCommerce extensions page
|
||||
- `WP_ADMIN_PLUGIN_INSTALL` - WordPress plugin install page
|
||||
|
||||
#### Front end
|
||||
|
||||
|
|
|
@ -12,8 +12,10 @@ export const WP_ADMIN_LOGIN = baseUrl + 'wp-login.php';
|
|||
export const WP_ADMIN_DASHBOARD = baseUrl + 'wp-admin/';
|
||||
export const WP_ADMIN_WP_UPDATES = WP_ADMIN_DASHBOARD + 'update-core.php';
|
||||
export const WP_ADMIN_PLUGINS = WP_ADMIN_DASHBOARD + 'plugins.php';
|
||||
export const WP_ADMIN_PLUGIN_INSTALL = WP_ADMIN_DASHBOARD + 'plugin-install.php';
|
||||
export const WP_ADMIN_PERMALINK_SETTINGS = WP_ADMIN_DASHBOARD + 'options-permalink.php';
|
||||
export const WP_ADMIN_ALL_USERS_VIEW = WP_ADMIN_DASHBOARD + 'users.php';
|
||||
|
||||
/**
|
||||
* WooCommerce core post type pages.
|
||||
* @type {string}
|
||||
|
@ -27,6 +29,7 @@ export const WP_ADMIN_NEW_ORDER = WP_ADMIN_NEW_POST_TYPE + 'shop_order';
|
|||
export const WP_ADMIN_ALL_PRODUCTS_VIEW = WP_ADMIN_POST_TYPE + 'product';
|
||||
export const WP_ADMIN_NEW_PRODUCT = WP_ADMIN_NEW_POST_TYPE + 'product';
|
||||
export const WP_ADMIN_IMPORT_PRODUCTS = WP_ADMIN_ALL_PRODUCTS_VIEW + '&page=product_importer';
|
||||
|
||||
/**
|
||||
* WooCommerce settings pages.
|
||||
* @type {string}
|
||||
|
@ -38,6 +41,7 @@ export const WP_ADMIN_ANALYTICS_PAGES = WP_ADMIN_WC_HOME + '&path=%2Fanalytics%2
|
|||
export const WP_ADMIN_WC_SETTINGS = WP_ADMIN_PLUGIN_PAGE + 'wc-settings&tab=';
|
||||
export const WP_ADMIN_NEW_SHIPPING_ZONE = WP_ADMIN_WC_SETTINGS + 'shipping&zone_id=new';
|
||||
export const WP_ADMIN_WC_EXTENSIONS = WP_ADMIN_PLUGIN_PAGE + 'wc-addons';
|
||||
|
||||
/**
|
||||
* Shop pages.
|
||||
* @type {string}
|
||||
|
@ -47,6 +51,7 @@ export const SHOP_PRODUCT_PAGE = baseUrl + '?p=';
|
|||
export const SHOP_CART_PAGE = baseUrl + 'cart';
|
||||
export const SHOP_CHECKOUT_PAGE = baseUrl + 'checkout/';
|
||||
export const SHOP_MY_ACCOUNT_PAGE = baseUrl + 'my-account/';
|
||||
|
||||
/**
|
||||
* Customer account pages.
|
||||
* @type {string}
|
||||
|
|
|
@ -6,6 +6,7 @@ const flowExpressions = require( './expressions' );
|
|||
const merchant = require( './merchant' );
|
||||
const shopper = require( './shopper' );
|
||||
const { withRestApi } = require( './with-rest-api' );
|
||||
const utils = require( './utils' );
|
||||
|
||||
module.exports = {
|
||||
...flowConstants,
|
||||
|
@ -13,4 +14,5 @@ module.exports = {
|
|||
merchant,
|
||||
shopper,
|
||||
withRestApi,
|
||||
utils,
|
||||
};
|
||||
|
|
|
@ -25,10 +25,13 @@ const {
|
|||
WP_ADMIN_ANALYTICS_PAGES,
|
||||
WP_ADMIN_ALL_USERS_VIEW,
|
||||
WP_ADMIN_IMPORT_PRODUCTS,
|
||||
WP_ADMIN_PLUGIN_INSTALL,
|
||||
WP_ADMIN_WP_UPDATES,
|
||||
IS_RETEST_MODE,
|
||||
} = require( './constants' );
|
||||
|
||||
const { getSlug } = require('./utils');
|
||||
|
||||
const baseUrl = config.get( 'url' );
|
||||
const WP_ADMIN_SINGLE_CPT_VIEW = ( postId ) => baseUrl + `wp-admin/post.php?post=${ postId }&action=edit`;
|
||||
|
||||
|
@ -276,7 +279,101 @@ const merchant = {
|
|||
page.waitForNavigation( { waitUntil: 'networkidle0' } ),
|
||||
]);
|
||||
}
|
||||
},
|
||||
|
||||
/* Uploads and activates a plugin located at the provided file path. This will also deactivate and delete the plugin if it exists.
|
||||
*
|
||||
* @param {string} pluginFilePath The location of the plugin zip file to upload.
|
||||
* @param {string} pluginName The name of the plugin. For example, `WooCommerce`.
|
||||
*/
|
||||
uploadAndActivatePlugin: async ( pluginFilePath, pluginName ) => {
|
||||
await merchant.openPlugins();
|
||||
|
||||
// Deactivate and delete the plugin if it exists
|
||||
let pluginSlug = getSlug( pluginName );
|
||||
if ( await page.$( `a#deactivate-${pluginSlug}` ) !== null ) {
|
||||
await merchant.deactivatePlugin( pluginName, true );
|
||||
}
|
||||
|
||||
// Open the plugin install page
|
||||
await page.goto( WP_ADMIN_PLUGIN_INSTALL, {
|
||||
waitUntil: 'networkidle0',
|
||||
} );
|
||||
|
||||
// Upload the plugin zip
|
||||
await page.click( 'a.upload-view-toggle' );
|
||||
|
||||
await expect( page ).toMatchElement(
|
||||
'p.install-help',
|
||||
{
|
||||
text: 'If you have a plugin in a .zip format, you may install or update it by uploading it here.'
|
||||
}
|
||||
);
|
||||
|
||||
const uploader = await page.$( 'input[type=file]' );
|
||||
|
||||
await uploader.uploadFile( pluginFilePath );
|
||||
|
||||
// Manually update the button to `enabled` so we can submit the file
|
||||
await page.evaluate(() => {
|
||||
document.getElementById( 'install-plugin-submit' ).disabled = false;
|
||||
});
|
||||
|
||||
// Click to upload the file
|
||||
await page.click( '#install-plugin-submit' );
|
||||
|
||||
await page.waitForNavigation( { waitUntil: 'networkidle0' } );
|
||||
|
||||
// Click to activate the plugin
|
||||
await page.click( '.button-primary' );
|
||||
|
||||
await page.waitForNavigation( { waitUntil: 'networkidle0' } );
|
||||
},
|
||||
|
||||
/**
|
||||
* Activate a given plugin by the plugin's name.
|
||||
*
|
||||
* @param {string} pluginName The name of the plugin to activate. For example, `WooCommerce`.
|
||||
*/
|
||||
activatePlugin: async ( pluginName ) => {
|
||||
let pluginSlug = getSlug( pluginName );
|
||||
|
||||
await expect( page ).toClick( `a#activate-${pluginSlug}` );
|
||||
|
||||
await page.waitForNavigation( { waitUntil: 'networkidle0' } );
|
||||
},
|
||||
|
||||
/**
|
||||
* Deactivate a plugin by the plugin's name with the option to delete the plugin as well.
|
||||
*
|
||||
* @param {string} pluginName The name of the plugin to deactivate. For example, `WooCommerce`.
|
||||
* @param {Boolean} deletePlugin Pass in `true` to delete the plugin. Defaults to `false`.
|
||||
*/
|
||||
deactivatePlugin: async ( pluginName, deletePlugin = false ) => {
|
||||
let pluginSlug = getSlug( pluginName );
|
||||
|
||||
await expect( page ).toClick( `a#deactivate-${pluginSlug}` );
|
||||
|
||||
await page.waitForNavigation( { waitUntil: 'networkidle0' } );
|
||||
|
||||
if ( deletePlugin ) {
|
||||
await merchant.deletePlugin( pluginName );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete a plugin by the plugin's name.
|
||||
*
|
||||
* @param {string} pluginName The name of the plugin to delete. For example, `WooCommerce`.
|
||||
*/
|
||||
deletePlugin: async ( pluginName ) => {
|
||||
let pluginSlug = getSlug( pluginName );
|
||||
|
||||
await expect( page ).toClick( `a#delete-${pluginSlug}` );
|
||||
|
||||
// Wait for Ajax calls to finish
|
||||
await page.waitForResponse( response => response.status() === 200 );
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = merchant;
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* Take a string name and generate the slug for it.
|
||||
* Example: 'My plugin' => 'my-plugin'
|
||||
*
|
||||
* Sourced from: https://gist.github.com/spyesx/561b1d65d4afb595f295
|
||||
**/
|
||||
export const getSlug = ( text ) => {
|
||||
text = text.trim().toLowerCase();
|
||||
|
||||
// remove accents, swap ñ for n, etc
|
||||
const from = 'åàáãäâèéëêìíïîòóöôùúüûñç·/_,:;';
|
||||
const to = 'aaaaaaeeeeiiiioooouuuunc------';
|
||||
|
||||
for (let i = 0, l = from.length; i < l; i++) {
|
||||
text = text.replace(new RegExp(from.charAt(i), "g"), to.charAt(i));
|
||||
}
|
||||
|
||||
return text
|
||||
.replace(/[^a-z0-9 -]/g, '') // remove invalid chars
|
||||
.replace(/\s+/g, '-') // collapse whitespace and replace by -
|
||||
.replace(/-+/g, '-') // collapse dashes
|
||||
.replace(/^-+/, '') // trim - from start of text
|
||||
.replace(/-+$/, '') // trim - from end of text
|
||||
.replace(/-/g, '-');
|
||||
};
|
||||
|
||||
// Conditionally determine whether or not to skip a test suite
|
||||
export const describeIf = ( condition ) =>
|
||||
condition ? describe : describe.skip;
|
||||
|
||||
// Conditionally determine whether or not to skip a test case
|
||||
export const itIf = ( condition ) =>
|
||||
condition ? it : it.skip;
|
Loading…
Reference in New Issue