diff --git a/.travis.yml b/.travis.yml index 0b0ae70e9e48..ff883dd7653f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -96,6 +96,7 @@ matrix: - sourceline: ppa:qameta/allure packages: - allure + - zip - name: "E2E tests with latest Gutenberg" language: generic @@ -108,7 +109,7 @@ matrix: - ./tests/e2e/bin/env.sh start - yarn test-decrypt-config script: - - yarn test-e2e + - yarn test-e2e --testPathIgnorePatterns=updater allow_failures: - name: "PHP Nightly" diff --git a/.wp-env.json b/.wp-env.json index 49c3ee1731fc..e3da98a00561 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -1,9 +1,13 @@ { - "plugins": [ ".", "./tests/e2e/plugins/e2e-plan-data-interceptor.php" ], + "plugins": [ "./tests/e2e/plugins/e2e-plan-data-interceptor.php" ], "config": { "WP_DEBUG_LOG": true, "WP_DEBUG_DISPLAY": false, "JETPACK_BETA_BLOCKS": true, "JETPACK_SHOULD_NOT_USE_CONNECTION_IFRAME": true + }, + "mappings": { + "wp-content/plugins/jetpack-dev": ".", + "wp-content/plugins/e2e-plugin-updater.php": "./tests/e2e/plugins/e2e-plugin-updater.php" } } diff --git a/bin/phpcs-requirelist.js b/bin/phpcs-requirelist.js index 4cf6bbb38963..7806f50811d3 100644 --- a/bin/phpcs-requirelist.js +++ b/bin/phpcs-requirelist.js @@ -76,7 +76,7 @@ module.exports = [ 'modules/wpcom-block-editor/class-jetpack-wpcom-block-editor.php', 'modules/wpcom-tos/wpcom-tos.php', 'packages', - 'tests/e2e/plugins/e2e-plan-data-interceptor.php', + 'tests/e2e/plugins', 'tests/php/general/test-class.jetpack-network.php', 'tests/php/test_class.jetpack_photon.php', 'views/admin/deactivation-dialog.php', diff --git a/tests/e2e/bin/cli-prep.sh b/tests/e2e/bin/cli-prep.sh new file mode 100755 index 000000000000..b864bd32f984 --- /dev/null +++ b/tests/e2e/bin/cli-prep.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +# Exit if any command fails. +set -e + +SITE_TITLE='E2E Testing' +WP_SITE_URL=${1} +WP_CORE_DIR=${2-$PWD} + +# Reset the database so no posts/comments/etc. +echo -e "Resetting test database..." +wp db reset --yes --quiet + +# Install WordPress. +echo -e "Installing WordPress..." +wp core install --title="$SITE_TITLE" --admin_user=wordpress --admin_password=wordpress --admin_email=test@example.com --skip-email --url=$WP_SITE_URL --path=$WP_CORE_DIR --quiet + +if [ -z $CI ]; then + echo -e "Setting up dynamic WP_HOME & SITE_URL..." + wp config set WP_SITEURL "'http://' . \$_SERVER['HTTP_HOST']" --raw --type=constant --quiet + wp config set WP_HOME "'http://' . \$_SERVER['HTTP_HOST']" --raw --type=constant --quiet +fi + +echo -e "Configuring site constants..." +wp config set WP_DEBUG true --raw --type=constant --quiet +wp config set WP_DEBUG_LOG true --raw --type=constant --quiet +wp config set WP_DEBUG_DISPLAY false --raw --type=constant --quiet +wp config set JETPACK_BETA_BLOCKS true --raw --type=constant --quiet + +# NOTE: Force classic connection flow +# https://github.com/Automattic/jetpack/pull/13288 +wp config set JETPACK_SHOULD_NOT_USE_CONNECTION_IFRAME true --raw --type=constant --quiet diff --git a/tests/e2e/bin/includes.sh b/tests/e2e/bin/includes.sh index 59f3ca8773b5..6f8edf583f31 100755 --- a/tests/e2e/bin/includes.sh +++ b/tests/e2e/bin/includes.sh @@ -135,6 +135,8 @@ configure_wp_env() { yarn wp-env run tests-cli wp plugin install gutenberg --activate fi + yarn wp-env run tests-cli wp plugin activate jetpack-dev + echo status_message "Open ${WP_SITE_URL} to see your site!" echo diff --git a/tests/e2e/bin/prep.sh b/tests/e2e/bin/prep.sh new file mode 100755 index 000000000000..7dadd74ad7c3 --- /dev/null +++ b/tests/e2e/bin/prep.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# Exit if any command fails. +set -e + +## +# This script creates a jetpack .zip that is accessible externaly via site/wp-content/jetpack.zip +# Also it creates a symlink from Jetpack directory to the wp-content/plugins + +# Parameters +WP_CORE_DIR="/var/www/html" +WORKING_DIR="$WP_CORE_DIR/wp-content/plugins/jetpack-dev" +ZIP_FILE="$WP_CORE_DIR/wp-content/uploads/jetpack.zip" +TMP_DIR="/tmp/jetpack" + +rm -rf $TMP_DIR $ZIP_FILE +mkdir -p $TMP_DIR + +FILES=$(ls -Ad $WORKING_DIR/* | grep -Ev "node_modules|docker|docs|extensions|.git") +cp -r $FILES $TMP_DIR + +if $(! type -t "zip" > /dev/null 2>&1); then + apt update > /dev/null + apt install zip -y > /dev/null +fi + +cd $(dirname "$TMP_DIR") + +zip -qr $ZIP_FILE jetpack/ +rm -rf $TMP_DIR + +echo "Done with jetpack.zip preparation!" diff --git a/tests/e2e/lib/flows/jetpack-connect.js b/tests/e2e/lib/flows/jetpack-connect.js index 2dafe0b957cc..83c7dc771772 100644 --- a/tests/e2e/lib/flows/jetpack-connect.js +++ b/tests/e2e/lib/flows/jetpack-connect.js @@ -117,6 +117,9 @@ export async function syncJetpackPlanData( plan, mockPlanData = true ) { export async function loginToWpSite( mockPlanData ) { const siteUrl = getNgrokSiteUrl(); + if ( ! siteUrl ) { + throw 'WOW, siteURL is empty!'; + } const host = new URL( siteUrl ).host; await ( await WPLoginPage.visit( page, siteUrl + '/wp-login.php' ) ).login(); if ( ! mockPlanData ) { diff --git a/tests/e2e/lib/pages/wp-admin/dashboard.js b/tests/e2e/lib/pages/wp-admin/dashboard.js index dd8cc3700691..b7e9234ca80e 100644 --- a/tests/e2e/lib/pages/wp-admin/dashboard.js +++ b/tests/e2e/lib/pages/wp-admin/dashboard.js @@ -3,11 +3,13 @@ */ import Page from '../page'; import { isEventuallyVisible, waitAndClick } from '../../page-helper'; +import { getNgrokSiteUrl } from '../../utils-helper'; export default class DashboardPage extends Page { constructor( page ) { const expectedSelector = '#dashboard-widgets-wrap'; - super( page, { expectedSelector } ); + const url = getNgrokSiteUrl() + '/wp-admin'; + super( page, { expectedSelector, url } ); } async isConnectBannerVisible() { diff --git a/tests/e2e/lib/pages/wp-admin/plugins.js b/tests/e2e/lib/pages/wp-admin/plugins.js index 29a6fe27d5ae..ae711ffb8945 100644 --- a/tests/e2e/lib/pages/wp-admin/plugins.js +++ b/tests/e2e/lib/pages/wp-admin/plugins.js @@ -2,7 +2,7 @@ * Internal dependencies */ import Page from '../page'; -import { waitAndClick, isEventuallyVisible } from '../../page-helper'; +import { waitAndClick, isEventuallyVisible, waitForSelector } from '../../page-helper'; export default class PluginsPage extends Page { constructor( page ) { @@ -31,4 +31,26 @@ export default class PluginsPage extends Page { const isConnectButtonVisible = await isEventuallyVisible( this.page, connectButtonSelector ); return isCardVisible && isConnectButtonVisible; } + + async getJetpackVersion() { + const versionText = 'tr.active[data-plugin="jetpack/jetpack.php"] .plugin-version-author-uri'; + const element = await waitForSelector( this.page, versionText ); + const text = await page.evaluate( e => e.textContent, element ); + return text.match( /\d.+?(?=\s)/ )[ 0 ]; + } + + async updateJetpack() { + await this.page.waitFor( 2000 ); + const updateCard = 'tr.active#jetpack-update[data-plugin="jetpack/jetpack.php"]'; + const updateLink = 'tr.active#jetpack-update[data-plugin="jetpack/jetpack.php"] .update-link'; + const isUpdatingMessage = + 'tr.active#jetpack-update[data-plugin="jetpack/jetpack.php"] .updating-message'; + + const updatedMessage = + 'tr.active#jetpack-update[data-plugin="jetpack/jetpack.php"] .updated-message'; + await waitForSelector( this.page, updateCard ); + await waitAndClick( this.page, updateLink ); + await waitForSelector( this.page, isUpdatingMessage ); + await waitForSelector( this.page, updatedMessage, { timeout: 3 * 30000 } ); + } } diff --git a/tests/e2e/lib/pages/wpcom/connections.js b/tests/e2e/lib/pages/wpcom/connections.js index 0785ffdf7ac0..fc7dc4158481 100644 --- a/tests/e2e/lib/pages/wpcom/connections.js +++ b/tests/e2e/lib/pages/wpcom/connections.js @@ -8,6 +8,7 @@ import { clickAndWaitForNewPage, getAccountCredentials, isEventuallyPresent, + waitForSelector, } from '../../page-helper'; export default class ConnectionsPage extends Page { @@ -17,11 +18,13 @@ export default class ConnectionsPage extends Page { } async selectMailchimpList( mailchimpList = 'e2etesting' ) { - const mailchimpExpandSelector = '.mailchimp .foldable-card__expand'; + const loadingIndicatorSelector = '.foldable-card__summary button:not([disabled])'; + const mailchimpExpandSelector = '.mailchimp .foldable-card__expand svg[height="24"]'; const marketingSelectSelector = '.mailchimp select'; const mcOptionXpathSelector = `//option[contains(text(), '${ mailchimpList }')]`; const successNoticeSelector = `//span[contains(text(), '${ mailchimpList }')]`; + await waitForSelector( this.page, loadingIndicatorSelector ); await waitAndClick( this.page, mailchimpExpandSelector ); // If user account is already connected to Mailchimp, we don't really need to connect it once again diff --git a/tests/e2e/lib/setup-env.js b/tests/e2e/lib/setup-env.js index 9442ca936706..dfba6a883bdf 100644 --- a/tests/e2e/lib/setup-env.js +++ b/tests/e2e/lib/setup-env.js @@ -22,18 +22,21 @@ const defaultErrorHandler = async ( error, name ) => { // If running tests in CI if ( CI ) { const filePath = await takeScreenshot( currentBlock, name ); - reporter.addAttachment( - `Test failed: ${ currentBlock } :: ${ name }`, - fs.readFileSync( filePath ), - 'image/png' - ); - logger.slack( { type: 'failure', message: { block: currentBlock, name, error }, } ); logger.slack( { type: 'file', message: filePath } ); await logDebugLog(); + try { + reporter.addAttachment( + `Test failed: ${ currentBlock } :: ${ name }`, + fs.readFileSync( filePath ), + 'image/png' + ); + } catch ( e ) { + logger.warn( `Failed to add attachment to allure report: ${ e }` ); + } } if ( E2E_LOG_HTML ) { diff --git a/tests/e2e/lib/utils-helper.js b/tests/e2e/lib/utils-helper.js index a213111f5836..b36ecfa83619 100644 --- a/tests/e2e/lib/utils-helper.js +++ b/tests/e2e/lib/utils-helper.js @@ -35,10 +35,14 @@ export function getNgrokSiteUrl() { } export async function resetWordpressInstall() { - let cmd = './tests/e2e/bin/docker-e2e-cli.sh reset'; - if ( process.env.CI ) { - cmd = './tests/e2e/bin/setup-e2e-travis.sh reset_wp'; - } + const cmd = './tests/e2e/bin/env.sh reset'; + await execShellCommand( cmd ); +} + +export async function prepareUpdaterTest() { + const cmd = + 'yarn wp-env run tests-wordpress wp-content/plugins/jetpack-dev/tests/e2e/bin/prep.sh'; + await execShellCommand( cmd ); } diff --git a/tests/e2e/plugins/e2e-plan-data-interceptor.php b/tests/e2e/plugins/e2e-plan-data-interceptor.php index 22e6d497025a..1728ec1c7431 100644 --- a/tests/e2e/plugins/e2e-plan-data-interceptor.php +++ b/tests/e2e/plugins/e2e-plan-data-interceptor.php @@ -19,6 +19,10 @@ * @param string $url request URL. */ function e2e_intercept_plan_data_request( $return, $r, $url ) { + if ( ! class_exists( 'Jetpack_Options' ) ) { + return $return; + } + $site_id = Jetpack_Options::get_option( 'id' ); if ( empty( $site_id ) ) { diff --git a/tests/e2e/plugins/e2e-plugin-updater.php b/tests/e2e/plugins/e2e-plugin-updater.php new file mode 100644 index 000000000000..e1b4d609f4b3 --- /dev/null +++ b/tests/e2e/plugins/e2e-plugin-updater.php @@ -0,0 +1,57 @@ + 'Version' ) )['Version']; + + if ( $current_version === $update_version ) { + // Already on desired Jetpack version. + return $value; + } + + // Override an existing Jetpack update. + if ( ! empty( $value->response['jetpack/jetpack.php'] ) ) { + $value->response['jetpack/jetpack.php']->new_version = $update_version; + $value->response['jetpack/jetpack.php']->package = $update_package; + return $value; + } + + // Cause a new Jetpack update. + if ( ! empty( $value->no_update['jetpack/jetpack.php'] ) ) { + $jetpack = $value->no_update['jetpack/jetpack.php']; + $jetpack->new_version = $update_version; + $jetpack->package = $update_package; + $value->response['jetpack/jetpack.php'] = $jetpack; + } + + return $value; +} diff --git a/tests/e2e/specs/plugin-updater.test.js b/tests/e2e/specs/plugin-updater.test.js new file mode 100644 index 000000000000..6968ba130f81 --- /dev/null +++ b/tests/e2e/specs/plugin-updater.test.js @@ -0,0 +1,51 @@ +/** + * Internal dependencies + */ +import { catchBeforeAll, step } from '../lib/setup-env'; +import { loginToWpSite, connectThroughWPAdminIfNeeded } from '../lib/flows/jetpack-connect'; +import { + execWpCommand, + prepareUpdaterTest, + getNgrokSiteUrl, + resetWordpressInstall, +} from '../lib/utils-helper'; +import Sidebar from '../lib/pages/wp-admin/sidebar'; +import PluginsPage from '../lib/pages/wp-admin/plugins'; + +describe( 'Jetpack updater', () => { + catchBeforeAll( async () => { + await prepareUpdaterTest(); + await execWpCommand( 'wp plugin deactivate jetpack-dev' ); + await execWpCommand( 'wp plugin install --activate jetpack' ); + await execWpCommand( 'wp plugin activate e2e-plugin-updater' ); + await execWpCommand( 'wp option set e2e_jetpack_upgrader_update_version 8.8-alpha' ); + const url = getNgrokSiteUrl(); + await execWpCommand( + `wp option set e2e_jetpack_upgrader_plugin_url ${ url }/wp-content/uploads/jetpack.zip` + ); + } ); + + afterAll( async () => { + await resetWordpressInstall(); + } ); + + it( 'Plugin updater', async () => { + await step( 'Can login and navigate to Plugins page', async () => { + await loginToWpSite(); + await ( await Sidebar.init( page ) ).selectInstalledPlugins(); + await PluginsPage.init( page ); + } ); + + await step( 'Can update Jetpack', async () => { + const pluginsPage = await PluginsPage.init( page ); + const versionBefore = await pluginsPage.getJetpackVersion(); + await pluginsPage.updateJetpack(); + const versionAfter = await pluginsPage.getJetpackVersion(); + expect( versionBefore ).not.toBe( versionAfter ); + } ); + + await step( 'Can connect Jetpack', async () => { + await connectThroughWPAdminIfNeeded( { mockPlanData: true, plan: 'free' } ); + } ); + } ); +} );