diff --git a/.eslintignore b/.eslintignore index 590187b9d5e..bb04dec2ca5 100644 --- a/.eslintignore +++ b/.eslintignore @@ -10,3 +10,4 @@ vendor/* release/* tests/e2e/docker* tests/e2e/deps* +tests/qit-e2e* \ No newline at end of file diff --git a/composer.json b/composer.json index e8ea75f318a..67ac47476da 100644 --- a/composer.json +++ b/composer.json @@ -42,7 +42,7 @@ "cweagans/composer-patches": "1.7.1", "automattic/jetpack-changelogger": "3.3.2", "spatie/phpunit-watcher": "1.23.6", - "woocommerce/qit-cli": "0.4.0", + "woocommerce/qit-cli": "^0.10.0", "slevomat/coding-standard": "8.15.0", "dg/bypass-finals": "1.5.1", "sirbrillig/phpcs-variable-analysis": "^2.11", diff --git a/composer.lock b/composer.lock index 553997998ff..6c06f12aa1c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "82ae2b86cd431fd736751ad4c4460abb", + "content-hash": "a08e5bc2af19b80b1b6bbea6f3d100bc", "packages": [ { "name": "automattic/jetpack-a8c-mc-stats", @@ -6746,16 +6746,16 @@ }, { "name": "woocommerce/qit-cli", - "version": "0.4.0", + "version": "0.10.0", "source": { "type": "git", "url": "https://github.com/woocommerce/qit-cli.git", - "reference": "8c71a1ffd67879d43bde45512bb7fe3ff399814b" + "reference": "42c4722bb71940dc0435103775439588e923e1cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/woocommerce/qit-cli/zipball/8c71a1ffd67879d43bde45512bb7fe3ff399814b", - "reference": "8c71a1ffd67879d43bde45512bb7fe3ff399814b", + "url": "https://api.github.com/repos/woocommerce/qit-cli/zipball/42c4722bb71940dc0435103775439588e923e1cd", + "reference": "42c4722bb71940dc0435103775439588e923e1cd", "shasum": "" }, "require": { @@ -6773,9 +6773,9 @@ "description": "A command line interface for WooCommerce Quality Insights Toolkit (QIT).", "support": { "issues": "https://github.com/woocommerce/qit-cli/issues", - "source": "https://github.com/woocommerce/qit-cli/tree/0.4.0" + "source": "https://github.com/woocommerce/qit-cli/tree/0.10.0" }, - "time": "2024-01-29T16:27:45+00:00" + "time": "2025-05-20T15:58:42+00:00" }, { "name": "woocommerce/woocommerce-sniffs", @@ -7019,9 +7019,9 @@ "php": ">=7.3", "ext-json": "*" }, - "platform-dev": [], + "platform-dev": {}, "platform-overrides": { "php": "7.3" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/includes/class-wc-payments.php b/includes/class-wc-payments.php index 35c1ac8aadc..04031930b66 100644 --- a/includes/class-wc-payments.php +++ b/includes/class-wc-payments.php @@ -781,6 +781,24 @@ function () { self::$duplicate_payment_prevention_service->init( self::$card_gateway, self::$order_service ); wcpay_get_container()->get( \WCPay\Internal\PluginManagement\TranslationsLoader::class )->init_hooks(); + + if ( defined( 'WP_CLI' ) && WP_CLI + && function_exists( 'wp_get_environment_type' ) + && in_array( wp_get_environment_type(), [ 'development', 'local' ], true ) + ) { + + require_once WCPAY_ABSPATH . 'includes/wp-cli/class-wp-cli-init-test-drive-account-command.php'; + $wp_cli_init_test_drive_account_command = new WP_CLI_Init_Test_Drive_Account_Command( self::$onboarding_service ); + WP_CLI::add_command( 'woopayments init-test-drive-account', $wp_cli_init_test_drive_account_command ); + + require_once WCPAY_ABSPATH . 'includes/wp-cli/class-wp-cli-disable-test-drive-account-command.php'; + $wp_cli_init_test_drive_account_command = new WP_CLI_Disable_Test_Drive_Account_Command( self::$onboarding_service ); + WP_CLI::add_command( 'woopayments disable-test-drive-account', $wp_cli_init_test_drive_account_command ); + + require_once WCPAY_ABSPATH . 'includes/wp-cli/class-wp-cli-set-blog-id-command.php'; + $wp_cli_set_blog_id_command = new WP_CLI_Set_Blog_Id_Command(); + WP_CLI::add_command( 'woopayments set_blog_id', $wp_cli_set_blog_id_command ); + } } /** diff --git a/includes/wp-cli/class-wp-cli-disable-test-drive-account-command.php b/includes/wp-cli/class-wp-cli-disable-test-drive-account-command.php new file mode 100644 index 00000000000..cf94fde536a --- /dev/null +++ b/includes/wp-cli/class-wp-cli-disable-test-drive-account-command.php @@ -0,0 +1,53 @@ +onboarding_service = $onboarding_service; + } + + /** + * Disable the test drive account. + * + * ## EXAMPLES + * + * wp woopayments disable-test-drive-account + * + * @when after_wp_load + * + * @param array $args Command line arguments. + * @param array $assoc_args Associative arguments. + * @return void + */ + public function __invoke( array $args, array $assoc_args ): void { + try { + $result = $this->onboarding_service->disable_test_drive_account( [ 'from' => 'cli' ] ); + if ( true === $result ) { + WP_CLI::success( 'Test drive account disabled successfully.' ); + } else { + WP_CLI::error( 'Failed to disable test drive account.' ); + } + } catch ( \Exception $e ) { + WP_CLI::error( 'Failed to disable test drive account: ' . $e->getMessage() ); + } + } +} diff --git a/includes/wp-cli/class-wp-cli-init-test-drive-account-command.php b/includes/wp-cli/class-wp-cli-init-test-drive-account-command.php new file mode 100644 index 00000000000..49eeb9079d2 --- /dev/null +++ b/includes/wp-cli/class-wp-cli-init-test-drive-account-command.php @@ -0,0 +1,71 @@ +onboarding_service = $onboarding_service; + } + + /** + * Initialize the test drive account. + * + * ## OPTIONS + * + * [--country=] + * : The country code for the test drive account. + * --- + * default: US + * --- + * + * ## EXAMPLES + * + * wp woopayments init-test-drive-account + * wp woopayments init-test-drive-account --country=GB + * + * @when after_wp_load + * + * @param array $args Command line arguments. + * @param array $assoc_args Associative arguments. + * @return void + */ + public function __invoke( array $args, array $assoc_args ): void { + $country = isset( $assoc_args['country'] ) ? $assoc_args['country'] : 'US'; + try { + Country_Code::search( $country ); + } catch ( \InvalidArgumentException $e ) { + WP_CLI::error( 'Invalid country code. Please provide a valid country code.' ); + } + + try { + $result = $this->onboarding_service->init_test_drive_account( $country ); + if ( true === $result ) { + WP_CLI::success( 'Test drive account initialized successfully.' ); + } else { + WP_CLI::error( 'Failed to initialize test drive account.' ); + } + } catch ( \Exception $e ) { + WP_CLI::error( 'Failed to initialize test drive account: ' . $e->getMessage() ); + } + } +} diff --git a/includes/wp-cli/class-wp-cli-set-blog-id-command.php b/includes/wp-cli/class-wp-cli-set-blog-id-command.php new file mode 100644 index 00000000000..e5676c93925 --- /dev/null +++ b/includes/wp-cli/class-wp-cli-set-blog-id-command.php @@ -0,0 +1,63 @@ + + * : The blog ID. + * + * [--blog_token=] + * : Jetpack blog token. Values should be wrapped in quotes. + * + * [--user_token=] + * : Jetpack user token. Values should be wrapped in quotes. + * --- + * + * ## EXAMPLES + * + * # Update Blog ID + * wp woopayments set_blog_id + * + * # Update Blog ID with blog & user tokens + * wp woopayments set_blog_id --blog_token= --user_token= + * + * @when after_wp_load + * @param array $args Positional arguments. + * @param array $assoc_args Associative arguments. + */ + public function __invoke( array $args, array $assoc_args ): void { + $blog_id = $args[0]; + if ( ! is_numeric( $blog_id ) ) { + WP_CLI::error( 'Please provide a numeric blog ID.' ); + } + + if ( ! class_exists( 'Jetpack_Options' ) ) { + WP_CLI::error( 'Jetpack_Options class does not exist. Please check your Jetpack installation.' ); + } + + $blog_token = ! empty( $assoc_args['blog_token'] ) ? $assoc_args['blog_token'] : '123.ABC'; + $user_token = [ + 1 => ! empty( $assoc_args['user_token'] ) ? $assoc_args['user_token'] : '123.ABC.1', + ]; + + Jetpack_Options::update_option( 'id', intval( $blog_id ) ); + Jetpack_Options::update_option( 'master_user', 1 ); + Jetpack_Options::update_option( 'blog_token', $blog_token ); + Jetpack_Options::update_option( 'user_tokens', $user_token ); + + WP_CLI::success( "Set Jetpack blog id to $blog_id" ); + } +} diff --git a/qit.json b/qit.json new file mode 100644 index 00000000000..9cf142550ca --- /dev/null +++ b/qit.json @@ -0,0 +1,18 @@ +{ + "wp": "rc", + "woo": "rc", + "php_version": "7.4", + "plugins": [ + "woocommerce", + "akismet", + "wordpress-importer" + ], + "themes": [ + "storefront", + "twentytwentyfour", + "twentytwentyfive" + ], + "volumes": [ + ".:/var/www/html/wp-content/plugins/woocommerce-payments" + ] +} \ No newline at end of file diff --git a/tests/e2e/README.md b/tests/e2e/README.md index 0fc12c134e6..7cfe6cdb0de 100644 --- a/tests/e2e/README.md +++ b/tests/e2e/README.md @@ -59,6 +59,8 @@ Jetpack_Options::get_option( 'blog_token' ); Jetpack_Options::get_option( 'user_tokens' ); ``` +Another option is to install Jetpack Beta Tester plugin, and enable Jetpack Debug. In the Jetpack Debug menu check the Broken Token and save. The Broken Token submenu will appear and will contain the aforementioned data. + Set the value of `E2E_USE_LOCAL_SERVER` to `false` to enable live server. Once you have the blog id & tokens, add the following env variables to your `local.env`. diff --git a/tests/qit-e2e/bootstrap/dependencies.json b/tests/qit-e2e/bootstrap/dependencies.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/tests/qit-e2e/bootstrap/dependencies.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/qit-e2e/bootstrap/mu-plugin.php b/tests/qit-e2e/bootstrap/mu-plugin.php new file mode 100644 index 00000000000..2189a5a9fc4 --- /dev/null +++ b/tests/qit-e2e/bootstrap/mu-plugin.php @@ -0,0 +1,13 @@ + { + // Example: Set up plugin settings via UI + // await page.goto('/wp-admin/admin.php?page=your-plugin-settings'); + // await page.fill('input[name="plugin_setting"]', 'test value'); + // await page.click('text=Save Changes'); +} ); diff --git a/tests/qit-e2e/bootstrap/setup.sh b/tests/qit-e2e/bootstrap/setup.sh new file mode 100644 index 00000000000..c5c9dc4ffdb --- /dev/null +++ b/tests/qit-e2e/bootstrap/setup.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +# This is an isolated setup script that runs before your plugin's tests. +# Use it to: +# - Create test data specific to your plugin +# - Set up plugin settings +# - Create temporary files +# - Set up test users or other resources needed for the tests + +# The WordPress installation is at "/var/www/html" +# This file is at: "/qit/tests/e2e///bootstrap/setup.sh" +# You can use relative paths to access files in your test. +# WP CLI is already configured, there's no need to use "--path /var/www/html" + +# echo "Running isolated setup for the plugin..." +# wp post create --post_title="Test Post" # Create a test post +# wp user create testuser test@example.com # Create a test user + +# Add your setup commands here +wp theme activate storefront + +echo "Set up WooCommerce settings via WP-CLI" +wp option set woocommerce_store_address "60 29th Street" +wp option set woocommerce_store_address_2 "#343" +wp option set woocommerce_store_city "San Francisco" +wp option set woocommerce_default_country "US:CA" +wp option set woocommerce_store_postcode "94110" +wp option set woocommerce_currency "USD" +wp option set woocommerce_product_type "both" +wp option set woocommerce_allow_tracking "no" +wp option set woocommerce_enable_signup_and_login_from_checkout "yes" + +echo "Import sample products" +wp import /var/www/html/wp-content/plugins/woocommerce/sample-data/sample_products.xml --authors=skip + +echo "Removing some WooCommerce Core 'tour' options so they don't interfere with tests" +wp option set woocommerce_orders_report_date_tour_shown yes + +echo "Disabling rate limiter for card declined in E2E tests" +wp option set wcpay_session_rate_limiter_disabled_wcpay_card_declined_registry yes + +echo "Dismissing fraud protection welcome tour in E2E tests" +wp option set wcpay_fraud_protection_welcome_tour_dismissed 1 + +echo "Enabling company field as an optional parameter in checkout form..." +wp option set woocommerce_checkout_company_field "optional" + +echo "Importing WooCommerce shop pages..." +wp wc --user=admin tool run install_pages + +echo "Set environment type to 'development'" +wp config set WP_ENVIRONMENT_TYPE development + +# Ensuring that the jetpack "account protection" feature is disabled, +# since the passwords for the locally run e2e tests can be allowed to be weak. +wp config set DISABLE_JETPACK_ACCOUNT_PROTECTION true --raw + +echo "Setting Jetpack blog_id" +wp woopayments set_blog_id "$E2E_JP_SITE_ID" --blog_token="'$E2E_JP_BLOG_TOKEN'" --user_token="'$E2E_JP_USER_TOKEN'" + +echo "Initialize WooPayments test drive account" +wp woopayments init-test-drive-account + +echo "Disabling rate limiter for card declined in E2E tests" +wp option set wcpay_session_rate_limiter_disabled_wcpay_card_declined_registry yes + +echo "Dismissing fraud protection welcome tour in E2E tests" +wp option set wcpay_fraud_protection_welcome_tour_dismissed 1 + +echo "Deactivating Coming Soon mode in WooCommerce..." +wp option set woocommerce_coming_soon "no" \ No newline at end of file diff --git a/tests/qit-e2e/example.spec.js b/tests/qit-e2e/example.spec.js new file mode 100644 index 00000000000..f49f9ac35fc --- /dev/null +++ b/tests/qit-e2e/example.spec.js @@ -0,0 +1,25 @@ +/* + * This is an example E2E test. You can write your own tests, or generate them with Codegen. + * + * Read more about it on our documentation: https://qit.woo.com/docs/custom-tests/generating-tests + */ + +/** + * External dependencies + */ +import { test, expect } from '@playwright/test'; +import qit from '/qitHelpers'; + +test( 'I can see my plugin menu', async ( { page } ) => { + // Log-in as admin. + await qit.loginAsAdmin( page ); + // View WordPress Core "Dashboard" heading + await expect( + page.getByRole( 'heading', { name: 'Dashboard' } ) + ).toBeVisible(); + // Click on my menu on the sidebar. + // await page.getByRole('link', { name: 'My Plugin Menu', exact: true }).click(); + // await page.waitForLoadState('networkidle'); + // Assert I see my welcome message when I click it. + // await expect(page.locator('h3')).toContainText('Welcome to My Plugin!'); +} ); diff --git a/tests/qit-e2e/specs/merchant/merchant-admin-account-balance.spec.ts b/tests/qit-e2e/specs/merchant/merchant-admin-account-balance.spec.ts new file mode 100644 index 00000000000..3b130f9eb6f --- /dev/null +++ b/tests/qit-e2e/specs/merchant/merchant-admin-account-balance.spec.ts @@ -0,0 +1,127 @@ +/** + * External dependencies + */ +import { test, expect } from '@playwright/test'; +import qit from '/qitHelpers'; + +// Optional currency symbol, followed by one or more digits, decimal separator, or comma. +const formattedCurrencyRegex = /[^\d.,]*[\d.,]+/; + +test.describe( 'Merchant account balance overview', () => { + test( + 'View the total and available account balance for a single deposit currency', + { + tag: '@critical', + }, + async ( { page } ) => { + await qit.loginAsAdmin( page ); + + await test.step( + 'Navigate to the Payments Overview screen', + async () => { + await page.goto( + '/wp-admin/admin.php?page=wc-admin&path=/payments/overview' + ); + } + ); + + await test.step( + 'Observe the total account balance, ensuring it has a formatted currency value', + async () => { + const totalBalanceValue = page.getByLabel( + 'Total balance', + { + exact: true, + } + ); + + await expect( totalBalanceValue ).toHaveText( + formattedCurrencyRegex + ); + } + ); + + await test.step( + 'Observe the available account balance, ensuring it has a formatted currency value', + async () => { + const availableFundsValue = page.getByLabel( + 'Available funds', + { + exact: true, + } + ); + + await expect( availableFundsValue ).toHaveText( + formattedCurrencyRegex + ); + } + ); + } + ); + + test( + 'View the total and available account balance for multiple deposit currencies', + { + tag: [ '@critical', '@todo' ], + annotation: [ + { + type: 'issue', + description: + 'https://github.com/Automattic/woocommerce-payments/issues/9188', + }, + { + type: 'description', + description: + 'Test requirements not yet met: A merchant account with multiple deposit currencies must be available in our e2e environment', + }, + ], + }, + async () => { + await test.step( 'Navigate to the Payments Overview screen', () => { + // @todo + } ); + + await test.step( + 'Select a deposit currency using the currency select input', + async () => { + // @todo + } + ); + + await test.step( + 'Observe the total account balance for the selected currency, ensuring it is correctly formatted with the currency symbol', + async () => { + // @todo + } + ); + + await test.step( + 'Observe the available account balance the selected currency, ensuring it is correctly formatted with the currency symbol', + async () => { + // @todo + } + ); + + await test.step( + 'Select a second deposit currency using the currency select input', + async () => { + // @todo + } + ); + + await test.step( + 'Observe the total account balance for the selected currency, ensuring it is correctly formatted with the currency symbol', + async () => { + // @todo + } + ); + + await test.step( + 'Observe the available account balance the selected currency, ensuring it is correctly formatted with the currency symbol', + async () => { + // @todo + } + ); + } + ); +} );