Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
c30457d
Refactor QIT development tools for WooPayments: update comments, enha…
mgascam Sep 16, 2025
e3064e3
Add E2E test credentials and enhance WooPayments setup scripts
mgascam Sep 17, 2025
9de0e5d
Merge branch 'dev/qit-e2e-foundation' into dev/qit-e2e-basic-checkout
mgascam Sep 18, 2025
e6b2e0f
Merge branch 'dev/qit-e2e-foundation' into dev/qit-e2e-basic-checkout
mgascam Sep 22, 2025
9023c4a
Merge branch 'dev/qit-e2e-foundation' into dev/qit-e2e-basic-checkout
mgascam Sep 22, 2025
97c0335
Merge branch 'dev/qit-e2e-foundation' into dev/qit-e2e-basic-checkout
mgascam Sep 23, 2025
34dd024
Merge branch 'dev/qit-e2e-foundation' into dev/qit-e2e-basic-checkout
mgascam Sep 24, 2025
c252170
Add wp-cli-stubs dependency and update psalm-loader for static analysis
mgascam Sep 24, 2025
d53a75a
Merge branch 'dev/qit-e2e-foundation' into dev/qit-e2e-basic-checkout
mgascam Oct 1, 2025
673c891
Merge branch 'dev/qit-e2e-foundation' into dev/qit-e2e-basic-checkout
mgascam Oct 1, 2025
7eb756c
Merge branch 'dev/qit-e2e-foundation' into dev/qit-e2e-basic-checkout
mgascam Oct 3, 2025
c5506bf
Merge branch 'dev/qit-e2e-foundation' into dev/qit-e2e-basic-checkout
mgascam Oct 7, 2025
b1935f7
WIP refactor cli commands
mgascam Oct 7, 2025
220ecbb
Refactor QIT Jetpack connection setup and status scripts
mgascam Oct 7, 2025
1ec81fa
Remove wp-cli-stubs dependency from composer.json and psalm-loader.php
mgascam Oct 7, 2025
33a294c
Add safety checks for JetPack commands and update environment setup s…
mgascam Oct 8, 2025
9f25161
Merge branch 'dev/qit-e2e-foundation' into dev/qit-e2e-basic-checkout
mgascam Oct 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions tests/qit/config/default.env
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,11 @@
QIT_USER=your_qit_username
QIT_PASSWORD=your_qit_application_password

# ===========================================
# E2E TEST CREDENTIALS (optional - for basic connectivity testing)
# ===========================================
# These provide basic WooPayments plugin connectivity for E2E tests
E2E_JP_SITE_ID=your_site_id_here
E2E_JP_BLOG_TOKEN=your_blog_token_here
E2E_JP_USER_TOKEN=your_user_token_here

82 changes: 72 additions & 10 deletions tests/qit/e2e-runner.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,63 @@ echo "Running E2E tests..."
# Change to project root directory to build plugin
cd "$WCP_ROOT"

# Foundation version: Simplified build process for easier testing
# For this foundation PR, we'll always build to avoid complex signature computation issues
# Compute a signature of sources relevant to the release build and
# skip rebuilding if nothing has changed since the last build.
compute_build_signature() {
# Hash tracked files that affect the release artifact. This includes
# sources packaged in the zip and build/config files that affect the output.
git ls-files -z -- \
assets \
i18n \
includes \
languages \
lib \
src \
templates \
client \
tasks/release.js \
webpack \
webpack.config.js \
babel.config.js \
package.json \
package-lock.json \
composer.json \
composer.lock \
woocommerce-payments.php \
changelog.txt \
readme.txt \
SECURITY.md \
2>/dev/null \
| xargs -0 shasum -a 256 2>/dev/null \
| shasum -a 256 \
| awk '{print $1}'

# Explicitly return 0 to avoid pipefail issues
return 0
}

BUILD_HASH_FILE="$WCP_ROOT/woocommerce-payments.zip.hash"

# For this foundation PR, always build if zip doesn't exist or if forced
if [[ -n "${WCP_FORCE_BUILD:-}" ]] || [[ ! -f "woocommerce-payments.zip" ]]; then
echo "Building WooPayments plugin..."
CURRENT_SIG="$(compute_build_signature)"

# If WCP_FORCE_BUILD is set, always rebuild
if [[ -n "${WCP_FORCE_BUILD:-}" ]]; then
echo "WCP_FORCE_BUILD set; forcing build of WooPayments plugin..."
npm run build:release
echo "foundation-build-$(date +%s)" > "$BUILD_HASH_FILE"
echo "$CURRENT_SIG" > "$BUILD_HASH_FILE"
elif [[ -f "woocommerce-payments.zip" && -f "$BUILD_HASH_FILE" ]]; then
LAST_SIG="$(cat "$BUILD_HASH_FILE" 2>/dev/null || true)"
if [[ "$CURRENT_SIG" == "$LAST_SIG" && -n "$CURRENT_SIG" ]]; then
echo "No relevant changes detected since last build; skipping build."
else
echo "Changes detected; rebuilding WooPayments plugin..."
npm run build:release
echo "$CURRENT_SIG" > "$BUILD_HASH_FILE"
fi
else
echo "Using existing woocommerce-payments.zip"
echo "Building WooPayments plugin..."
npm run build:release
echo "$CURRENT_SIG" > "$BUILD_HASH_FILE"
fi

# Change to QIT directory so qit.yml is automatically found
Expand All @@ -46,10 +91,27 @@ else
QIT_CMD="$QIT_BINARY"
fi

echo "Running QIT E2E foundation tests (no Jetpack credentials)..."
# Pass basic Jetpack environment variables
env_args=()
if [[ -n "${E2E_JP_SITE_ID:-}" ]]; then
env_args+=( --env "E2E_JP_SITE_ID=${E2E_JP_SITE_ID}" )
fi
if [[ -n "${E2E_JP_BLOG_TOKEN:-}" ]]; then
env_args+=( --env "E2E_JP_BLOG_TOKEN=${E2E_JP_BLOG_TOKEN}" )
fi
if [[ -n "${E2E_JP_USER_TOKEN:-}" ]]; then
env_args+=( --env "E2E_JP_USER_TOKEN=${E2E_JP_USER_TOKEN}" )
fi

# Run our QIT E2E tests (qit.yml automatically loaded from current directory)
"$QIT_CMD" run:e2e woocommerce-payments ./e2e \
--source "$WCP_ROOT/woocommerce-payments.zip"
echo "Running QIT E2E tests with Jetpack functionality..."
if [ ${#env_args[@]} -eq 0 ]; then
"$QIT_CMD" run:e2e woocommerce-payments ./e2e \
--source "$WCP_ROOT/woocommerce-payments.zip"
else
"$QIT_CMD" run:e2e woocommerce-payments ./e2e \
--source "$WCP_ROOT/woocommerce-payments.zip" \
"${env_args[@]}"
fi

echo "QIT E2E foundation tests completed!"
28 changes: 17 additions & 11 deletions tests/qit/e2e/basic.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,13 @@ test( 'Load home page', async ( { page } ) => {
} );

/**
* Test WooCommerce Payments onboarding flow access
* Since we're running in development mode without Jetpack connection,
* we expect to always land on the onboarding flow.
* Test admin authentication and WooPayments plugin access
*/
test( 'Access WooCommerce Payments onboarding as admin', async ( { page } ) => {
test( 'Access WooPayments as admin', async ( { page } ) => {
// Use QIT helper to login as admin
await qit.loginAsAdmin( page );

// Navigate to WooCommerce Payments settings
// Navigate to WooPayments settings
await page.goto(
'/wp-admin/admin.php?page=wc-admin&path=%2Fpayments%2Foverview'
);
Expand All @@ -33,13 +31,21 @@ test( 'Access WooCommerce Payments onboarding as admin', async ( { page } ) => {
page.locator( 'h1:not(.screen-reader-text)' ).first()
).toContainText( /Settings|Payments|Overview/, { timeout: 15000 } );

// In development mode without Jetpack connection, we should be on onboarding
expect( page.url() ).toContain( 'onboarding' );
// Check that we can successfully load the WooPayments interface
// Either we get the overview (if fully connected) OR the onboarding.
const isOnboarding = page.url().includes( 'onboarding' );
const isOverview = page.url().includes( 'payments' );

// The onboarding page should load without errors
await expect( page.locator( 'body' ) ).not.toHaveText(
/500|404|Fatal error/
);
// We should be on either the onboarding or overview page (both indicate success)
expect( isOnboarding || isOverview ).toBe( true );

// If we're on onboarding, it should be functional (not errored)
if ( isOnboarding ) {
// The onboarding page should load without errors
await expect( page.locator( 'body' ) ).not.toHaveText(
/500|404|Fatal error/
);
}
} );

/**
Expand Down
205 changes: 205 additions & 0 deletions tests/qit/e2e/bootstrap/class-wp-cli-qit-dev-command.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
<?php
/**
* QIT Dev Tools command
*
* @package WooCommerce\Payments
*/

use WCPay\Database_Cache;

/**
* QIT development tools for WooPayments E2E testing.
* Provides Jetpack connection setup for QIT environments.
*/
class WP_CLI_QIT_Dev_Command {
/**
* Establishes Jetpack connection for WooPayments QIT testing.
*
* ## OPTIONS
*
* <blog_id>
* : Numeric blog ID from WordPress.com.
*
* [--blog_token=<value>]
* : Jetpack blog token.
*
* [--user_token=<value>]
* : Jetpack user token.
*
* ## EXAMPLES
* wp woopayments qit_jetpack_connection 248403234 --blog_token=abc123 --user_token=def456
*
* @param array $args Positional arguments passed to the command.
* @param array $assoc_args Associative arguments passed to the command.
*/
public function qit_jetpack_connection( array $args, array $assoc_args ): void {
// Safety check: Only allow in local/development environments.
$environment_type = function_exists( 'wp_get_environment_type' ) ? wp_get_environment_type() : 'production';
if ( 'local' !== $environment_type && 'development' !== $environment_type ) {
\WP_CLI::error( 'This command can only be run in local or development environments for safety.' );
}

if ( empty( $args[0] ) || ! is_numeric( $args[0] ) ) {
\WP_CLI::error( 'Please provide a numeric blog ID.' );
}

if ( ! class_exists( 'Jetpack_Options' ) ) {
\WP_CLI::error( 'Jetpack_Options class does not exist. Ensure Jetpack is installed and active.' );
}

$blog_id = (int) $args[0];
$blog_token = isset( $assoc_args['blog_token'] ) ? (string) $assoc_args['blog_token'] : '123.ABC.QIT';
$user_token = isset( $assoc_args['user_token'] ) ? (string) $assoc_args['user_token'] : '123.ABC.QIT.1';

// Force test mode BEFORE any other operations (since this is a test account).
$this->force_test_mode();

// Set up Jetpack connection.
$this->setup_jetpack_connection( $blog_id, $blog_token, $user_token );

// Enable dev mode (like WCP Dev Tools plugin does).
$this->enable_dev_mode();

// Refresh account data to get real account info from server (like regular E2E tests).
if ( class_exists( 'WC_Payments' ) ) {
$this->refresh_account_data();
}

\WP_CLI::success( "Jetpack connection established for blog ID {$blog_id}" );
\WP_CLI::line( 'Account data fetched from server based on Jetpack connection' );
}

/**
* Shows Jetpack connection status for WooPayments QIT testing.
*
* @when after_wp_load
*/
public function qit_jetpack_status(): void {
// Safety check: Only allow in local/development environments.
$environment_type = function_exists( 'wp_get_environment_type' ) ? wp_get_environment_type() : 'production';
if ( 'local' !== $environment_type && 'development' !== $environment_type ) {
\WP_CLI::error( 'This command can only be run in local or development environments for safety.' );
}

\WP_CLI::line( '=== QIT Jetpack Connection Status ===' );

if ( class_exists( 'Jetpack_Options' ) ) {
$blog_id = Jetpack_Options::get_option( 'id' );
\WP_CLI::line( 'Blog ID: ' . ( $blog_id ? $blog_id : 'Not Set' ) );
}

if ( class_exists( 'WC_Payments' ) ) {
$database_cache = \WC_Payments::get_database_cache();
if ( $database_cache ) {
$account_data = $database_cache->get( Database_Cache::ACCOUNT_KEY );
\WP_CLI::line( 'Account Data: ' . ( $account_data ? 'Present' : 'Not Set' ) );
}
}

\WP_CLI::line( 'Dev Mode: ' . ( get_option( 'wcpaydev_dev_mode' ) ? 'Enabled' : 'Disabled' ) );
}

/**
* Configures Jetpack connection options.
*
* @param int $blog_id WordPress.com blog ID.
* @param string $blog_token Jetpack blog token.
* @param string $user_token Jetpack user token.
*/
private function setup_jetpack_connection( int $blog_id, string $blog_token, string $user_token ): void {
$user_tokens = [ 1 => $user_token ];

Jetpack_Options::update_option( 'id', $blog_id );
Jetpack_Options::update_option( 'master_user', 1 );
Jetpack_Options::update_option( 'blog_token', $blog_token );
Jetpack_Options::update_option( 'user_tokens', $user_tokens );

\WP_CLI::log( "Jetpack connection configured for blog ID {$blog_id}" );
}

/**
* Enables WCP development mode like the WCP Dev Tools plugin.
*/
private function enable_dev_mode(): void {
// Enable dev mode like WCP Dev Tools plugin does.
update_option( 'wcpaydev_dev_mode', '1' );

// Add the dev mode filter like WCP Dev Tools plugin does.
add_filter( 'wcpay_dev_mode', '__return_true' );

\WP_CLI::log( 'Enabled WCPay dev mode with filter' );
}

/**
* Forces WCP test mode by setting filters and gateway settings.
*
* DEFENSE IN DEPTH STRATEGY:
* This method uses multiple independent mechanisms to ensure test mode is active.
* While WP_ENVIRONMENT_TYPE=development automatically enables dev mode (see WCPay\Core\Mode),
* we explicitly set test mode through multiple layers for maximum safety:
*
* 1. WordPress filters - Override mode detection at runtime
* 2. Gateway settings - Persist test mode in database
* 3. Onboarding service - Set test mode at service layer
*
* This redundancy protects against:
* - Changes to Mode class logic
* - Filter overrides by other code
* - Environment variable changes
* - Accidental live mode activation
*
* All mechanisms must fail for live mode to activate - acceptable tradeoff for test safety.
*/
private function force_test_mode(): void {
// Force test mode onboarding and test mode since we're using a test account.
add_filter( 'wcpay_test_mode_onboarding', '__return_true' );
add_filter( 'wcpay_test_mode', '__return_true' );

// Also try setting the gateway settings to enable test mode.
$gateway_settings = get_option( 'woocommerce_woocommerce_payments_settings', [] );
$gateway_settings['test_mode'] = 'yes';
update_option( 'woocommerce_woocommerce_payments_settings', $gateway_settings );

// CRITICAL: Use WC_Payments_Onboarding_Service to set test mode (this sets test_mode_onboarding).
if ( class_exists( 'WC_Payments_Onboarding_Service' ) ) {
\WC_Payments_Onboarding_Service::set_test_mode( true );
\WP_CLI::log( 'Set WC_Payments_Onboarding_Service test mode - this enables test_mode_onboarding' );
}

\WP_CLI::log( 'Forced WCPay test mode for test account (filters + gateway settings + onboarding service)' );
}

/**
* Refreshes account data from the WCP server and validates the connection.
*/
private function refresh_account_data(): void {
if ( ! class_exists( 'WC_Payments' ) ) {
\WP_CLI::log( 'WC_Payments not available - skipping account refresh' );
return;
}

try {
$account_service = \WC_Payments::get_account_service();
\WP_CLI::log( 'Attempting to refresh account data...' );

$result = $account_service->refresh_account_data();

// Check if data was actually set.
$database_cache = \WC_Payments::get_database_cache();
$account_data = $database_cache ? $database_cache->get( Database_Cache::ACCOUNT_KEY ) : null;

if ( $account_data ) {
\WP_CLI::log( 'Account data refreshed successfully from server' );
// Verify key fields exist without exposing sensitive data.
$has_account_id = isset( $account_data['account_id'] ) && ! empty( $account_data['account_id'] );
$has_keys = isset( $account_data['live_publishable_key'] ) || isset( $account_data['test_publishable_key'] );
$status = $account_data['status'] ?? 'unknown';
\WP_CLI::log( 'Account validation: ID=' . ( $has_account_id ? 'present' : 'missing' ) . ', Keys=' . ( $has_keys ? 'present' : 'missing' ) . ', Status=' . $status );
} else {
\WP_CLI::warning( 'Account refresh completed but no account data cached - connection may be invalid' );
}
} catch ( \Exception $e ) {
\WP_CLI::warning( 'Account refresh failed: ' . $e->getMessage() );
}
}
}
Loading
Loading