Merge pull request #31400 from woocommerce/add/plugin-upload-test-functionality

Added plugin upload and test functionality
This commit is contained in:
Ron Rennick 2021-12-17 12:22:33 -04:00 committed by GitHub
commit fe5d4d6c2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 285 additions and 18 deletions

View File

@ -52,3 +52,103 @@ jobs:
pnpx wc-e2e test:e2e plugins/woocommerce/tests/e2e/specs/smoke-tests/update-woocommerce.js pnpx wc-e2e test:e2e plugins/woocommerce/tests/e2e/specs/smoke-tests/update-woocommerce.js
pnpx wc-e2e test:e2e pnpx wc-e2e test:e2e
pnpx wc-api-tests test api pnpx wc-api-tests test api
build:
name: Build zip for PR
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Build
id: build
uses: woocommerce/action-build@trunk
env:
BUILD_ENV: e2e
- name: Upload PR zip
uses: actions/upload-artifact@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
name: woocommerce
path: ${{ steps.build.outputs.zip_path }}
retention-days: 7
test-plugins:
name: Smoke tests with ${{ matrix.plugin }} plugin installed
runs-on: ubuntu-18.04
needs: [build]
strategy:
fail-fast: false
matrix:
include:
- plugin: 'WooCommerce Payments'
repo: 'automattic/woocommerce-payments'
- plugin: 'WooCommerce PayPal Payments'
repo: 'woocommerce/woocommerce-paypal-payments'
- plugin: 'WooCommerce Shipping & Tax'
repo: 'woocommerce/woocommerce-services'
- plugin: 'WooCommerce Subscriptions'
repo: WC_SUBSCRIPTIONS_REPO
private: true
- plugin: 'WordPress SEO' # Yoast SEO in the UI, but the slug is wordpress-seo
repo: 'Yoast/wordpress-seo'
- plugin: 'Contact Form 7'
repo: 'takayukister/contact-form-7'
steps:
- name: Create dirs.
run: |
mkdir -p code/woocommerce
mkdir -p package/woocommerce
mkdir -p tmp/woocommerce
mkdir -p node_modules
- name: Checkout code.
uses: actions/checkout@v2
with:
path: package/woocommerce
- name: Install PNPM and install dependencies
working-directory: package/woocommerce
run: |
npm install -g pnpm
pnpm install
- name: Load docker images and start containers.
working-directory: package/woocommerce/plugins/woocommerce
run: pnpx wc-e2e docker:up
- name: Move current directory to code. We will install zip file in this dir later.
run: mv ./package/woocommerce/plugins/woocommerce/* ./code/woocommerce
- name: Download WooCommerce ZIP.
uses: actions/download-artifact@v2
with:
name: woocommerce
path: tmp
- name: Extract and replace WooCommerce zip.
working-directory: tmp
run: |
unzip woocommerce.zip -d woocommerce
mv woocommerce/woocommerce/* ../package/woocommerce/plugins/woocommerce/
- name: Install dependencies again
working-directory: package/woocommerce
run: |
npm install -g pnpm
pnpm install
- name: Run tests command.
working-directory: package/woocommerce/plugins/woocommerce
env:
WC_E2E_SCREENSHOTS: 1
E2E_SLACK_TOKEN: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }}
E2E_SLACK_CHANNEL: ${{ secrets.RELEASE_TEST_SLACK_CHANNEL }}
GITHUB_REPOSITORY: ${{ matrix.private && secrets[matrix.repo] || matrix.repo }}
PLUGIN_NAME: ${{ matrix.plugin }}
GITHUB_TOKEN: ${{ secrets.E2E_GH_TOKEN }}
run: |
pnpx wc-e2e test:e2e plugins/woocommerce/tests/e2e/specs/smoke-tests/upload-plugin.js
pnpm nx test-e2e woocommerce

View File

@ -105,3 +105,74 @@ jobs:
E2E_SLACK_TOKEN: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }} E2E_SLACK_TOKEN: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }}
E2E_SLACK_CHANNEL: ${{ secrets.RELEASE_TEST_SLACK_CHANNEL }} E2E_SLACK_CHANNEL: ${{ secrets.RELEASE_TEST_SLACK_CHANNEL }}
run: pnpm nx test-e2e woocommerce run: pnpm nx test-e2e woocommerce
test-plugins:
name: Smoke tests with ${{ matrix.plugin }} plugin installed
runs-on: ubuntu-18.04
strategy:
fail-fast: false
matrix:
include:
- plugin: 'WooCommerce Payments'
repo: 'automattic/woocommerce-payments'
- plugin: 'WooCommerce PayPal Payments'
repo: 'woocommerce/woocommerce-paypal-payments'
- plugin: 'WooCommerce Shipping & Tax'
repo: 'woocommerce/woocommerce-services'
- plugin: 'WooCommerce Subscriptions'
repo: WC_SUBSCRIPTIONS_REPO
private: true
- plugin: 'WordPress SEO' # Yoast SEO in the UI, but the slug is wordpress-seo
repo: 'Yoast/wordpress-seo'
- plugin: 'Contact Form 7'
repo: 'takayukister/contact-form-7'
steps:
- name: Create dirs.
run: |
mkdir -p code/woocommerce
mkdir -p package/woocommerce
mkdir -p tmp/woocommerce
mkdir -p node_modules
- name: Checkout code.
uses: actions/checkout@v2
with:
path: package/woocommerce
- name: Install PNPM and install dependencies
working-directory: package/woocommerce
run: |
npm install -g pnpm
pnpm install
- name: Load docker images and start containers.
working-directory: package/woocommerce/plugins/woocommerce
env:
LATEST_WP_VERSION_MINUS: ${{ matrix.wp }}
run: pnpm nx docker-up woocommerce
- name: Move current directory to code. We will install zip file in this dir later.
run: mv ./package/woocommerce/plugins/woocommerce/* ./code/woocommerce
- name: Download WooCommerce release zip
working-directory: tmp
run: |
ASSET_ID=$(jq ".release.assets[0].id" $GITHUB_EVENT_PATH)
curl https://api.github.com/repos/woocommerce/woocommerce/releases/assets/${ASSET_ID} -LJOH 'Accept: application/octet-stream'
unzip woocommerce.zip -d woocommerce
mv woocommerce/woocommerce/* ../package/woocommerce/plugins/woocommerce/
- name: Run tests command.
working-directory: package/woocommerce/plugins/woocommerce
env:
WC_E2E_SCREENSHOTS: 1
E2E_SLACK_TOKEN: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }}
E2E_SLACK_CHANNEL: ${{ secrets.RELEASE_TEST_SLACK_CHANNEL }}
GITHUB_REPOSITORY: ${{ matrix.private && secrets[matrix.repo] || matrix.repo }}
PLUGIN_NAME: ${{ matrix.plugin }}
GITHUB_TOKEN: ${{ secrets.E2E_GH_TOKEN }}
run: |
pnpx wc-e2e test:e2e plugins/woocommerce/tests/e2e/specs/smoke-tests/upload-plugin.js
pnpm nx test-e2e woocommerce

View File

@ -3,6 +3,8 @@
## Added ## Added
- Added `await` for every call to `shopper.logout` - Added `await` for every call to `shopper.logout`
- Updated `getLatestReleaseZipUrl()` to allow passing in an authorization token and simplified arguments to just the repository name
- Added `upload.ini` which increases the limits for uploading files (such as for plugins) in the Docker environment
- Test setup, scaffolding, and removal via `wc-e2e install` and `wc-e2e uninstall` - Test setup, scaffolding, and removal via `wc-e2e install` and `wc-e2e uninstall`
## Fixed ## Fixed

View File

@ -254,9 +254,9 @@ The above method also makes use of the following utility methods which can also
If you would like to get the latest release zip URL, which can be used in the methods mentioned above, you can use the following helper function to do so: If you would like to get the latest release zip URL, which can be used in the methods mentioned above, you can use the following helper function to do so:
`getLatestReleaseZipUrl( owner, repository, getPrerelease, perPage )` `getLatestReleaseZipUrl( repository, authorizationToken, getPrerelease, perPage )`
This will return a string with the latest release URL. Optionally, you can use the `getPrerelease` boolean flag, which defaults to false, on whether or not to get a prerelease instead. The `perPage` flag can be used to return more results when getting the list of releases. The default value is 3. This will return a string with the latest release URL. Optionally, you can use the `getPrerelease` boolean flag, which defaults to false, on whether or not to get a prerelease instead. The `perPage` flag can be used to return more results when getting the list of releases. The default value is 3. If the repository requires authorization to access, the authorization token can be passed in to the `authorizationToken` argument.
## Additional information ## Additional information

View File

@ -35,6 +35,7 @@ services:
WORDPRESS_DEBUG: 1 WORDPRESS_DEBUG: 1
volumes: volumes:
- wordpress:/var/www/html - wordpress:/var/www/html
- ./upload.ini:/usr/local/etc/php/conf.d/uploads.ini
- "../../../${WC_E2E_FOLDER}:${WC_E2E_FOLDER_MAPPING}" - "../../../${WC_E2E_FOLDER}:${WC_E2E_FOLDER_MAPPING}"
wordpress-cli: wordpress-cli:

View File

@ -0,0 +1,5 @@
file_uploads = On
memory_limit = 500M
upload_max_filesize = 500M
post_max_size = 500M
max_execution_time = 600

View File

@ -1,27 +1,27 @@
const path = require( 'path' ); const path = require( 'path' );
const getAppRoot = require( './app-root' );
const fs = require( 'fs' ); const fs = require( 'fs' );
const mkdirp = require( 'mkdirp' ); const mkdirp = require( 'mkdirp' );
const request = require( 'request' ); const request = require( 'request' );
const StreamZip = require( 'node-stream-zip' ); const StreamZip = require( 'node-stream-zip' );
const { resolveLocalE2ePath } = require( './test-config' );
/** /**
* Upload a plugin zip from a remote location, such as a GitHub URL or other hosted location. * 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. * @param {string} fileUrl The URL where the zip file is located.
* @param {string} authorizationToken Authorization token used to authenticate with the GitHub API if required.
* @return {string} The path where the zip file is located. * @return {string} The path where the zip file is located.
*/ */
const getRemotePluginZip = async ( fileUrl ) => { const getRemotePluginZip = async ( fileUrl, authorizationToken = '' ) => {
const appPath = getAppRoot();
const savePath = resolveLocalE2ePath( 'plugins' ); const savePath = resolveLocalE2ePath( 'plugins' );
mkdirp.sync( savePath ); mkdirp.sync( savePath );
// Pull the filename from the end of the URL // Pull the version from the end of the URL
const fileName = fileUrl.split( '/' ).pop(); const fileName = fileUrl.split( '/' ).pop();
let filePath = path.join( savePath, fileName ); let filePath = path.join( savePath, fileName );
// First, download the zip file // First, download the zip file
await downloadZip( fileUrl, filePath ); await downloadZip( fileUrl, filePath, authorizationToken );
// Check for a nested zip and update the filepath // Check for a nested zip and update the filepath
filePath = await checkNestedZip( filePath, savePath ); filePath = await checkNestedZip( filePath, savePath );
@ -32,24 +32,24 @@ const getRemotePluginZip = async ( fileUrl ) => {
/** /**
* Get the latest release zip for a plugin from a GiHub repository. * Get the latest release zip for a plugin from a GiHub repository.
* *
* @param {string} owner The owner of the plugin repository. * @param {string} repository The repository owner and name. For example: `woocommerce/woocommerce`.
* @param {string} repository The repository name. * @param {string} authorizationToken Authorization token used to authenticate with the GitHub API if required.
* @param {boolean} getPrerelease Flag on whether to get a prelease or not. * @param {boolean} getPrerelease Flag on whether to get a prelease or not.
* @param {number} perPage Limit of entries returned from the latest releases list, defaults to 3. * @param {number} perPage Limit of entries returned from the latest releases list, defaults to 3.
* @return {Promise<string>}} Returns the URL for the release zip file. * @return {Promise<string>}} Returns the URL for the release zip file.
*/ */
const getLatestReleaseZipUrl = async ( const getLatestReleaseZipUrl = async (
owner,
repository, repository,
authorizationToken = '',
getPrerelease = false, getPrerelease = false,
perPage = 3 perPage = 3
) => { ) => {
let requesturl; let requesturl;
if ( getPrerelease ) { if ( getPrerelease ) {
requesturl = `https://api.github.com/repos/${ owner }/${ repository }/releases?per_page=${ perPage }`; requesturl = `https://api.github.com/repos/${ repository }/releases?per_page=${ perPage }`;
} else { } else {
requesturl = `https://api.github.com/repos/${ owner }/${ repository }/releases/latest`; requesturl = `https://api.github.com/repos/${ repository }/releases/latest`;
} }
const options = { const options = {
@ -59,6 +59,11 @@ const getLatestReleaseZipUrl = async (
headers: { 'user-agent': 'node.js' }, headers: { 'user-agent': 'node.js' },
}; };
// If provided with a token, use it for authorization
if ( authorizationToken ) {
options.headers.Authorization = `token ${ authorizationToken }`;
}
// Wrap in a promise to make the request async // Wrap in a promise to make the request async
return new Promise( function ( resolve, reject ) { return new Promise( function ( resolve, reject ) {
request.get( options, function ( err, resp, body ) { request.get( options, function ( err, resp, body ) {
@ -71,6 +76,12 @@ const getLatestReleaseZipUrl = async (
resolve( release.assets[ 0 ].browser_download_url ); resolve( release.assets[ 0 ].browser_download_url );
} }
} ); } );
} else if ( authorizationToken ) {
// If it's a private repo, we need to download the archive this way
const tagName = body.tag_name;
resolve(
`https://github.com/${ repository }/archive/${ tagName }.zip`
);
} else { } else {
resolve( body.assets[ 0 ].browser_download_url ); resolve( body.assets[ 0 ].browser_download_url );
} }
@ -90,7 +101,7 @@ const checkNestedZip = async ( zipFilePath, savePath ) => {
const entries = await zip.entries(); const entries = await zip.entries();
for ( const entry of Object.values( entries ) ) { for ( const entry of Object.values( entries ) ) {
if ( entry.name.match( /.zip/ ) ) { if ( entry.name.match( /\.zip/ ) ) {
await zip.extract( null, savePath ); await zip.extract( null, savePath );
await zip.close(); await zip.close();
return path.join( savePath, entry.name ); return path.join( savePath, entry.name );
@ -105,15 +116,22 @@ const checkNestedZip = async ( zipFilePath, savePath ) => {
* *
* @param {string} fileUrl The URL where the zip file is located. * @param {string} fileUrl The URL where the zip file is located.
* @param {string} downloadPath The location where to download the zip to. * @param {string} downloadPath The location where to download the zip to.
* @param {string} authorizationToken Authorization token used to authenticate with the GitHub API if required.
* @return {Promise<void>} * @return {Promise<void>}
*/ */
const downloadZip = async ( fileUrl, downloadPath ) => { const downloadZip = async ( fileUrl, downloadPath, authorizationToken ) => {
const options = { const options = {
url: fileUrl, url: fileUrl,
method: 'GET', method: 'GET',
encoding: null, encoding: null,
headers: { 'user-agent': 'node.js' },
}; };
// If provided with a token, use it for authorization
if ( authorizationToken ) {
options.headers.Authorization = `token ${ authorizationToken }`;
}
// Wrap in a promise to make the request async // Wrap in a promise to make the request async
return new Promise( function ( resolve, reject ) { return new Promise( function ( resolve, reject ) {
request request
@ -128,9 +146,27 @@ const downloadZip = async ( fileUrl, downloadPath ) => {
} ); } );
}; };
/**
* Delete the downloaded plugin files.
*/
const deleteDownloadedPluginFiles = async () => {
const pluginSavePath = resolveLocalE2ePath( 'plugins' );
fs.readdir( pluginSavePath, ( err, files ) => {
if ( err ) throw err;
for ( const file of files ) {
fs.unlink( path.join( pluginSavePath, file ), ( error ) => {
if ( error ) throw error;
} );
}
} );
};
module.exports = { module.exports = {
getRemotePluginZip, getRemotePluginZip,
getLatestReleaseZipUrl, getLatestReleaseZipUrl,
checkNestedZip, checkNestedZip,
downloadZip, downloadZip,
deleteDownloadedPluginFiles,
}; };

View File

@ -1,9 +1,13 @@
const getAppRoot = require( './app-root' ); const getAppRoot = require( './app-root' );
const { getAppName, getAppBase } = require( './app-name' ); const { getAppName, getAppBase } = require( './app-name' );
const testConfig = require( './test-config' ); const testConfig = require( './test-config' );
const { getRemotePluginZip, getLatestReleaseZipUrl } = require('./get-plugin-zip'); const {
getRemotePluginZip,
getLatestReleaseZipUrl,
deleteDownloadedPluginFiles,
} = require( './get-plugin-zip' );
const takeScreenshotFor = require( './take-screenshot' ); const takeScreenshotFor = require( './take-screenshot' );
const updateReadyPageStatus = require('./update-ready-page'); const updateReadyPageStatus = require( './update-ready-page' );
const consoleUtils = require( './filter-console' ); const consoleUtils = require( './filter-console' );
module.exports = { module.exports = {
@ -12,6 +16,7 @@ module.exports = {
getAppName, getAppName,
getRemotePluginZip, getRemotePluginZip,
getLatestReleaseZipUrl, getLatestReleaseZipUrl,
deleteDownloadedPluginFiles,
takeScreenshotFor, takeScreenshotFor,
updateReadyPageStatus, updateReadyPageStatus,
...testConfig, ...testConfig,

View File

@ -3,7 +3,7 @@
*/ */
const { merchant, utils } = require( '@woocommerce/e2e-utils' ); const { merchant, utils } = require( '@woocommerce/e2e-utils' );
const { getRemotePluginZip, getLatestReleaseZipUrl } = require( '@woocommerce/e2e-environment' ); const { getRemotePluginZip, getLatestReleaseZipUrl, deleteDownloadedPluginFiles } = require( '@woocommerce/e2e-environment' );
/** /**
* External dependencies * External dependencies
@ -24,7 +24,7 @@ utils.describeIf( UPDATE_WC )( 'WooCommerce plugin can be uploaded and activated
beforeAll( async () => { beforeAll( async () => {
if ( TEST_RELEASE ) { if ( TEST_RELEASE ) {
zipUrl = await getLatestReleaseZipUrl( 'woocommerce', 'woocommerce' ); zipUrl = await getLatestReleaseZipUrl( 'woocommerce/woocommerce' );
} else { } else {
zipUrl = 'https://github.com/woocommerce/woocommerce/releases/download/nightly/woocommerce-trunk-nightly.zip'; zipUrl = 'https://github.com/woocommerce/woocommerce/releases/download/nightly/woocommerce-trunk-nightly.zip';
} }
@ -35,6 +35,7 @@ utils.describeIf( UPDATE_WC )( 'WooCommerce plugin can be uploaded and activated
afterAll( async () => { afterAll( async () => {
await merchant.logout(); await merchant.logout();
await deleteDownloadedPluginFiles();
}); });
it( 'can upload and activate the WooCommerce plugin', async () => { it( 'can upload and activate the WooCommerce plugin', async () => {
@ -46,4 +47,8 @@ utils.describeIf( UPDATE_WC )( 'WooCommerce plugin can be uploaded and activated
await merchant.runDatabaseUpdate(); await merchant.runDatabaseUpdate();
}); });
it( 'can remove downloaded plugin zip', async () => {
await deleteDownloadedPluginFiles();
} );
}); });

View File

@ -0,0 +1,42 @@
/**
* Internal dependencies
*/
const { merchant, utils } = require( '@woocommerce/e2e-utils' );
const { getRemotePluginZip, getLatestReleaseZipUrl, deleteDownloadedPluginFiles } = require( '@woocommerce/e2e-environment' );
/**
* External dependencies
*/
const {
it,
beforeAll,
} = require( '@jest/globals' );
const { GITHUB_REPOSITORY, PLUGIN_NAME, GITHUB_TOKEN } = process.env;
let zipUrl;
let pluginPath;
utils.describeIf( GITHUB_REPOSITORY )( 'Upload and activate plugin', () => {
beforeAll( async () => {
zipUrl = await getLatestReleaseZipUrl( GITHUB_REPOSITORY, GITHUB_TOKEN );
pluginPath = await getRemotePluginZip( zipUrl, GITHUB_TOKEN );
await merchant.login();
});
afterAll( async () => {
await merchant.logout();
});
it( 'can upload and activate the provided plugin', async () => {
await merchant.uploadAndActivatePlugin( pluginPath, PLUGIN_NAME );
});
it( 'can remove downloaded plugin zip', async () => {
await deleteDownloadedPluginFiles();
} );
});