diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 8392a956a..fb9470b3f 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -42,6 +42,9 @@ jobs: shell: bash run: composer install --no-progress + - name: Add partner for QIT + run: ./vendor/bin/qit partner:add --user='${{ secrets.PARTNER_USER }}' --application_password='${{ secrets.PARTNER_SECRET }}' + # Node - name: Setup Node @@ -71,27 +74,30 @@ jobs: npm run build # E2E test environment - - - name: Prepare test environment - shell: bash - env: - GITHUB_TOKEN: ${{ secrets.E2E_GH_TOKEN }} - STRIPE_PUB_KEY: ${{ secrets.E2E_STRIPE_PUBLISHABLE_KEY }} - STRIPE_SECRET_KEY: ${{ secrets.E2E_STRIPE_SECRET_KEY }} - run: npm run test:e2e-setup + - name: Fill in .env + run: | + echo 'STRIPE_PUB_KEY="${{ secrets.E2E_STRIPE_PUBLISHABLE_KEY }}"' >> .env + echo 'STRIPE_SECRET_KEY="${{ secrets.E2E_STRIPE_SECRET_KEY }}"' >> .env + echo 'PAYPAL_MERCHANT_ID="${{ secrets.PAYPAL_MERCHANT_ID }}"' >> .env + echo 'PAYPAL_MERCHANT_EMAIL="${{ secrets.PAYPAL_MERCHANT_EMAIL }}"' >> .env + echo 'PAYPAL_CLIENT_ID="${{ secrets.PAYPAL_CLIENT_ID }}"' >> .env + echo 'PAYPAL_CLIENT_SECRET="${{ secrets.PAYPAL_CLIENT_SECRET }}"' >> .env + echo 'PAYPAL_CUSTOMER_EMAIL="${{ secrets.PAYPAL_CUSTOMER_EMAIL }}"' >> .env + echo 'PAYPAL_CUSTOMER_PASSWORD="${{ secrets.PAYPAL_CUSTOMER_PASSWORD }}"' >> .env - name: Run ${{ matrix.checkout }} E2E tests shell: bash - env: - STRIPE_PUB_KEY: ${{ secrets.E2E_STRIPE_PUBLISHABLE_KEY }} - STRIPE_SECRET_KEY: ${{ secrets.E2E_STRIPE_SECRET_KEY }} - run: npm run test:e2e${{ matrix.checkout == 'Legacy' && '-legacy' || '' }} + run: npm run test:e2e${{ matrix.checkout == 'Legacy' && '-legacy' || '' }} -- --env_file .env --plugin woocommerce-paypal-payments:bootstrap + - name: Set the path in an env var + if: ${{ failure() }} + run: echo "E2E_REPORT_PATH=$(./vendor/bin/qit e2e-report --dir_only --local)/playwright" >> $GITHUB_ENV + - name: Upload ${{ matrix.checkout }} E2E test results if: ${{ failure() }} uses: actions/upload-artifact@v4 with: - name: ${{ matrix.checkout }}-WP_latest-WC_latest-results - path: tests/e2e/test-results + name: ${{ matrix.checkout }}-test-results + path: ${{ env.E2E_REPORT_PATH }} if-no-files-found: ignore retention-days: 14 diff --git a/.github/workflows/qit-e2e-tests-upload.yml b/.github/workflows/qit-e2e-tests-upload.yml new file mode 100644 index 000000000..853af4a48 --- /dev/null +++ b/.github/workflows/qit-e2e-tests-upload.yml @@ -0,0 +1,30 @@ +name: Upload QIT E2E tests + +on: + workflow_dispatch: + release: + types: [published] + +jobs: + release: + name: New release + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set PHP version + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + tools: composer:v2 + coverage: none + + - name: Install QIT via composer + run: composer require woocommerce/qit-cli + + - name: Add partner + run: ./vendor/bin/qit partner:add --user='${{ secrets.PARTNER_USER }}' --application_password='${{ secrets.PARTNER_SECRET }}' + + - name: Upload E2E tests tags to QIT + run: npm run test:upload-tags diff --git a/composer.json b/composer.json index 7055cf94b..ff1f73dcd 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,8 @@ "phpunit/phpunit": "7.5.20", "yoast/phpunit-polyfills": "^2.0", "woocommerce/woocommerce-sniffs": "0.1.0", - "wp-cli/wp-cli-bundle": "2.5.0" + "wp-cli/wp-cli-bundle": "2.5.0", + "woocommerce/qit-cli": "^0.7.1" }, "extra": { "installer-disable": true diff --git a/composer.lock b/composer.lock index 62dd4da0d..1e7e4fd04 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": "ce8e7d358061604e3feaeb46c9a8b909", + "content-hash": "3935eb992b100f5ece69d9ff892483f1", "packages": [], "packages-dev": [ { @@ -4687,6 +4687,39 @@ }, "time": "2022-06-03T18:03:27+00:00" }, + { + "name": "woocommerce/qit-cli", + "version": "0.7.1", + "source": { + "type": "git", + "url": "https://github.com/woocommerce/qit-cli.git", + "reference": "124a4fc100d0eb1486622ab3e2ffb24c31f5ba9b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/woocommerce/qit-cli/zipball/124a4fc100d0eb1486622ab3e2ffb24c31f5ba9b", + "reference": "124a4fc100d0eb1486622ab3e2ffb24c31f5ba9b", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": "^7.2.5 | ^8" + }, + "bin": [ + "qit" + ], + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-3.0-or-later" + ], + "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.7.1" + }, + "time": "2024-10-30T21:07:50+00:00" + }, { "name": "woocommerce/woocommerce-sniffs", "version": "0.1.0", @@ -6989,5 +7022,5 @@ "platform-overrides": { "php": "7.4" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } diff --git a/package.json b/package.json index 3206a09c6..32ffffa85 100644 --- a/package.json +++ b/package.json @@ -151,14 +151,12 @@ "jt:setup": "npm run up && ./bin/jurassic-tube-setup.sh", "jt:start": "./docker/bin/jt/tunnel.sh", "jt:stop": "./docker/bin/jt/tunnel.sh break", - "test:e2e-setup": "./tests/e2e/bin/setup.sh", - "test:e2e-up": "./tests/e2e/bin/up.sh", - "test:e2e-down": "./tests/e2e/bin/down.sh", - "test:e2e-cleanup": "npm run test:e2e-down && ./tests/e2e/bin/cleanup.sh", - "test:e2e": "./tests/e2e/bin/run-tests.sh --project=default", - "test:e2e-debug": "npm run test:e2e -- --debug", - "test:e2e-legacy": "./tests/e2e/bin/run-tests.sh --project=legacy", - "test:e2e-legacy-debug": "npm run test:e2e-legacy -- --debug" + "test:e2e": "./vendor/bin/qit run:e2e woocommerce-gateway-stripe ./tests/e2e --source ./ --pw_options=\"--grep-invert @legacy\"", + "test:e2e-legacy": "./vendor/bin/qit run:e2e woocommerce-gateway-stripe ./tests/e2e --source ./ --pw_options=\"--grep @legacy\"", + "test:e2e-local": "./vendor/bin/qit run:e2e woocommerce-gateway-stripe ./tests/e2e --source ./ --pw_options=\"--grep-invert @legacy\" --env_file ./tests/e2e/config/local.env", + "test:e2e-legacy-local": "./vendor/bin/qit run:e2e woocommerce-gateway-stripe ./tests/e2e --source ./ --pw_options=\"--grep @legacy\" --env_file ./tests/e2e/config/local.env", + "test:e2e-all-local": "./vendor/bin/qit run:e2e woocommerce-gateway-stripe ./tests/e2e --source ./ --env_file ./tests/e2e/config/local.env", + "test:upload-tags": "sh ./tests/e2e/bin/upload-tags.sh" }, "engines": { "node": ">=16.17.0", diff --git a/qit-env.json b/qit-env.json new file mode 100644 index 000000000..f204ba9dd --- /dev/null +++ b/qit-env.json @@ -0,0 +1,14 @@ +{ + "php_version": "7.4", + "plugins": { + "woocommerce": { + "action": "bootstrap" + }, + "woocommerce-subscriptions": { + "action": "bootstrap" + } + }, + "themes": [ + "storefront" + ] +} \ No newline at end of file diff --git a/tests/e2e/README.md b/tests/e2e/README.md index d92afb4e0..9923fcdcb 100644 --- a/tests/e2e/README.md +++ b/tests/e2e/README.md @@ -1,6 +1,6 @@ # WooCommerce Gateway Stripe End to End Tests -We use [Playwright](https://playwright.dev/) as our test runner. +We use [QIT Custom E2E tests](https://qit.woo.com/docs/custom-tests/introduction) to run our end to end tests. ## Table of contents @@ -10,7 +10,7 @@ We use [Playwright](https://playwright.dev/) as our test runner. - [Pre-requisites](#pre-requisites) - [Environment Setup](#environment-setup) - [Running tests](#running-tests) - - [Debugging tests](#debugging-tests) + - [Compatibility Tests](#compatibility-tests) - [Running only selected test suites](#running-only-selected-test-suites) - [Guide for writing e2e tests](#guide-for-writing-e2e-tests) - [Creating the test structure](#creating-the-test-structure) @@ -22,13 +22,8 @@ We use [Playwright](https://playwright.dev/) as our test runner. - Node.js ([Installation instructions](https://nodejs.org/en/download/)) - NVM ([Installation instructions](https://github.com/nvm-sh/nvm)) -- A test site to run the tests on. **Jurassic Ninja sites are recommended**. -- Admin credentials (`wp-admin`) to the test site. - -**For the automated setup (optional)** - -- SSH access to the test site. -- WP CLI available on the test site server. +- QIT CLI +- Docker - Test keys for a Stripe account. ### Environment Setup @@ -37,52 +32,21 @@ We use [Playwright](https://playwright.dev/) as our test runner. - Edit the variables on the `local.env` file. ### Running tests +To set up the test environment and run test, run following commands: -**Test Setup** - -To set up the test environment, run the command: - -`npm run test:e2e-setup -- --base_url=SOME_URL_HERE` - -This command will perform the following actions: - -- Connect to the test server using SSH and the credentials in the `/tests/e2e/config/local.env` file. -- Install the latest version of WooCommerce from the official WordPress repository. -- Install the latest version of the WooCommerce Gateway Stripe plugin from the official WordPress repository. - **Note:** you can specify a different version to test by using the `--version` flag. In this case, the plugin will be downloaded from GitHub instead. -- Install and activate the StoreFront theme. -- Configure WooCommerce on the test site (e.g. store address, currency, shipping methods). -- Import test products into WooCommerce. -- Create pages for the Cart blocks and Checkout blocks from WooCommerce Blocks. -- Set up the Stripe gateway using the keys from the `/tests/e2e/config/local.env` file and create a webhook endpoint on Stripe. - -**Note:** To run this command, SSH and admin credentials are required. - -The SSH and admin credentials are mandatory (view the parameters `--with_woo_setup` and `--with_stripe_setup` below for more info). - -**Test execution** - -`npm run test:e2e -- --base_url=SOME_URL_HERE` - -The default command to run the tests. It'll run the tests in the URL indicated by the `--base_url` parameter. - -**Optional Parameters** - -`--version`: Allows you to specify a specific plugin version to test. This will download the specified version from GitHub and upload it to the test site before running the tests. If no version is specified, the tests will use the version currently installed on the test site. - -`--with_woo_setup`: Use this option when setting up a test site for the first time. It will use the SSH credentials from `tests/e2e/config/local.env` to set up the WooCommerce plugin with test products, store address, currency, and shipping methods, as well as installing and activating the StoreFront theme. - -`--with_stripe_setup`: Use this option when setting up a test site for the first time. It will use the Stripe keys from `tests/e2e/config/local.env` to set up the plugin, create a webhook endpoint on Stripe, and set up the webhook secret in the Stripe plugin. - -**⚠️ All the other parameters are passed to the Playwright CLI** - -[Playwright CLI Docs](https://playwright.dev/docs/test-cli) +To run tests locally: +- **Default tests:** `npm run test:e2e-local` +- **Legacy tests:** `npm run test:e2e-legacy-local` +- **All tests:** `npm run test:e2e-all-local` -### Debugging tests +To run tests in CI: +- **Default tests:** `npm run test:e2e -- --env_file ./PATH_TO_ENV_FILE` +- **Legacy tests:** `npm run test:e2e-legacy -- --env_file ./PATH_TO_ENV_FILE` -`npm run test:e2e-debug` +### Compatibility tests +QIT Custom E2E tests provide a way to run tests for other plugins. To test plugin compatibility with other Woo extensions you have access to, you can use the `--plugin` flag. You can find more information on this [here](https://qit.woo.com/docs/custom-tests/running-other-plugins-tests): -[Documentation](https://playwright.dev/docs/debug) +`npm run test:e2e -- --plugin woocommerce-paypal-payments:test` ### Running only selected test suites @@ -90,15 +54,15 @@ The default command to run the tests. It'll run the tests in the URL indicated b Certain tests are annotated to indicate their specific focus, such as subscriptions, blocks, or smoke tests. These annotations are indicated in the test name with the `@` symbol in front of them, for example `Test XYZ @subscriptions`. -To only run tests with a specific annotation, use the `--grep @annotation` parameter when running the tests. For example: +To only run tests with a specific annotation, use the `--grep @annotation` in the `pw_options` parameter when running the tests. For example: -`npm run test:e2e -- --base_url=SOME_URL_HERE --grep @subscriptions` +`npm run test:e2e-all-local -- --pw_options="--grep @subscriptions"` **Running Tests by File Name** You can also run tests by specifying the file name containing the test you want to run. Keep in mind that there may be duplicate file names, especially between tests run in the regular checkout and in the blocks checkout. - `npm run test:e2e -- --base_url=SOME_URL_HERE normal-card` +`npm run test:e2e-all-local -- --pw_options="normal-card"` In the above example, the command would run the tests with a file name containing `normal-card`. diff --git a/tests/e2e/bin/cleanup.sh b/tests/e2e/bin/cleanup.sh deleted file mode 100755 index f5255ad08..000000000 --- a/tests/e2e/bin/cleanup.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash -set -e - -. ./tests/e2e/bin/common.sh - -rm -rf $E2E_ROOT/e2e-setup.log - -step "Removing E2E docker folder" -rm -rf $E2E_ROOT/env/docker - -step "Removing result folders" -rm -rf $E2E_ROOT/test-results diff --git a/tests/e2e/bin/common.sh b/tests/e2e/bin/common.sh deleted file mode 100755 index 103c22e32..000000000 --- a/tests/e2e/bin/common.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env bash - -CWD=$(pwd) -E2E_ROOT="$CWD/tests/e2e" - -ADMIN_USER=${ADMIN_USER-admin} -ADMIN_PASSWORD=${ADMIN_PASSWORD-admin} -ADMIN_EMAIL=${ADMIN_EMAIL-admin@example.com} - -# -- - -error() { - echo - echo -e "\033[0;31mERROR\033[0m $1" -} - -step() { - echo - echo -e "\033[0;34m=>\033[0m $1" -} - -check_dep() { - if ! $1 --version > /dev/null 2>&1; then - echo - error "This script needs \033[0;34m$1\033[0m, please install it and try again." - exit 1 - fi -} - -redirect_output() { - if [[ "$DEBUG" = true ]]; then - "$@" - else - "$@" >> ${E2E_ROOT}/e2e-setup.log 2>&1 - fi -} - -# --user xfs forces the wordpress:cli container to use a user with the same ID as the main wordpress container. -# See: https://hub.docker.com/_/wordpress#running-as-an-arbitrary-user -cli() { - docker run -i --rm --user 33:33 --env-file ${E2E_ROOT}/env/default.env --volumes-from "wcstripe-e2e-wordpress" --network container:"wcstripe-e2e-wordpress" wordpress:cli "$@" -} diff --git a/tests/e2e/bin/down.sh b/tests/e2e/bin/down.sh deleted file mode 100755 index 2ca7daca9..000000000 --- a/tests/e2e/bin/down.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash -set -e - -. ./tests/e2e/bin/common.sh - -step "Stopping E2E docker containers" -CWD="$CWD" redirect_output docker-compose -p wcstripe-e2e down diff --git a/tests/e2e/bin/run-tests.sh b/tests/e2e/bin/run-tests.sh deleted file mode 100755 index 74569f263..000000000 --- a/tests/e2e/bin/run-tests.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bash - -set -e -. ./tests/e2e/bin/common.sh - -if [[ -f "$E2E_ROOT/config/local.env" ]]; then - . "$E2E_ROOT/config/local.env" -fi - -# If --base_url argument is present use the remote server setup. -if [[ "$*" == *"--base_url"* ]]; then - $E2E_ROOT/env/e2e.sh "$@" - exit -fi - -# If no --base_url received, run the tests against the E2E docker containers. - -TEST_ENV="NODE_CONFIG_DIR='tests/e2e/test-data'" - -TEST_ARGS="" -accepted_args=("--base_url") -for arg in "$@"; do - key=$(echo $arg | cut -f1 -d=) - value=$(echo $arg | cut -f2 -d=) - - if [[ ${accepted_args[*]} =~ "${key}" ]]; then - v="${key/--/}" - declare $v="${value}" - else - TEST_ARGS="$TEST_ARGS $arg" - fi -done - -if [[ *"wordpress" == "$(docker-compose -p wcstripe-e2e ps --services --filter "status=running" | grep wordpress)" ]]; then - error "Docker E2E containers are not running, please start them with 'npm run test:e2e-up' or 'npm run test:e2e-setup' and try again." - exit 1 -fi - -set -e - -. ./tests/e2e/bin/common.sh - -cd "$CWD" - -TEST_ENV="$TEST_ENV DOCKER=true E2E_ROOT=${E2E_ROOT} BASE_URL='http://localhost:8088'" -TEST_ENV="$TEST_ENV ADMIN_USER='admin' ADMIN_PASSWORD='admin'" - -cross-env $TEST_ENV playwright test --config=tests/e2e/config/playwright.config.js $TEST_ARGS diff --git a/tests/e2e/bin/setup.sh b/tests/e2e/bin/setup.sh deleted file mode 100755 index 39ba9ab14..000000000 --- a/tests/e2e/bin/setup.sh +++ /dev/null @@ -1,159 +0,0 @@ -#!/usr/bin/env bash - -set -e -. ./tests/e2e/bin/common.sh - -if [[ -f "$E2E_ROOT/config/local.env" ]]; then - . "$E2E_ROOT/config/local.env" -fi - -# If --base_url argument is present use the remote server setup. -if [[ "$*" == *"--base_url"* ]]; then - $E2E_ROOT/env/e2e.sh --with_woo_setup --with_stripe_setup "$@" - exit -fi - -# If no --base_url received, setup the docker test environment. - -DEBUG=false - -# Override custom user/password from local.env, if any. -ADMIN_USER=admin -ADMIN_PASSWORD=admin - -cd "$CWD" - -check_dep 'docker' -check_dep 'curl' -check_dep 'jq' - -if ! docker info > /dev/null 2>&1; then - echo - error "Docker is not running, please start it and try again." - exit 1 -fi - -step "Starting E2E docker containers" -CWD="$CWD" E2E_ROOT="$E2E_ROOT" redirect_output docker compose -p wcstripe-e2e -f "$E2E_ROOT"/env/docker-compose.yml up --build --force-recreate -d wordpress - -step "Configuring Wordpress" -# Wait for containers to be started up before setup. -# The db being accessible means that the db container started and the WP has been downloaded and the plugin linked -set +e -redirect_output cli wp db check --path=/var/www/html --quiet -while [[ $? -ne 0 ]]; do - echo " - Waiting for containers..." - sleep 5 - redirect_output cli wp db check --path=/var/www/html --quiet -done -set -e - -redirect_output cli wp core install \ - --path=/var/www/html \ - --url="http://localhost:8088" \ - --title="WCStripe E2E test store" \ - --admin_name="${ADMIN_USER}" \ - --admin_password="${ADMIN_PASSWORD}" \ - --admin_email="${ADMIN_EMAIL}" \ - --skip-email - -if [[ -n "$WP_VERSION" && "$WP_VERSION" != "latest" ]]; then - echo " - Installing Wordpress ${WP_VERSION}..." - redirect_output cli wp core update --version="$WP_VERSION" --force --quiet -else - echo " - Updating Wordpress to the latest version" - redirect_output cli wp core update --quiet -fi - -echo " - Updating the database" -redirect_output cli wp core update-db --quiet - -echo " - Disabling debug mode" -redirect_output cli wp config set WP_DEBUG false --raw - -echo " - Updating permalink structure" -redirect_output cli wp rewrite structure '/%postname%/' - -echo " - Installing Wordpress Importer" -redirect_output cli wp plugin install wordpress-importer --activate - -echo " - Disable emails to avoid spamming" -redirect_output cli wp plugin install disable-emails --activate - -# Install WooCommerce -if [[ -n "$WC_VERSION" && $WC_VERSION != 'latest' ]]; then - # If specified version is 'beta', fetch the latest beta version from WordPress.org API - if [[ $WC_VERSION == 'beta' ]]; then - WC_VERSION=$(curl https://api.wordpress.org/plugins/info/1.0/woocommerce.json | jq -r '.versions | with_entries(select(.key|match("beta";"i"))) | keys[-1]' --sort-keys) - fi - step "Installing WooCommerce ${WC_VERSION}" - redirect_output cli wp plugin install woocommerce --version="$WC_VERSION" --activate -else - step "Installing WooCommerce" - redirect_output cli wp plugin install woocommerce --activate -fi - -step "Configuring WooCommerce" -redirect_output cli wp option set woocommerce_store_address "60 29th Street" -redirect_output cli wp option set woocommerce_store_address_2 "#343" -redirect_output cli wp option set woocommerce_store_city "San Francisco" -redirect_output cli wp option set woocommerce_default_country "US:CA" -redirect_output cli wp option set woocommerce_store_postcode "94110" -redirect_output cli wp option set woocommerce_currency "USD" -redirect_output cli wp option set woocommerce_product_type "both" -redirect_output cli wp option set woocommerce_allow_tracking "no" -redirect_output cli wp option set woocommerce_coming_soon "no" - -echo " - Installing Storefront theme" -redirect_output cli wp theme install storefront --activate - -redirect_output cli wp wc --user=${ADMIN_USER} tool run install_pages - -echo " - Configuring Shipping and Taxes" -redirect_output cli wp wc shipping_zone create --name="Everywhere" --order=1 --user=${ADMIN_USER} -redirect_output cli wp wc shipping_zone_method create 1 --method_id="flat_rate" --user=${ADMIN_USER} -redirect_output cli wp wc shipping_zone_method create 1 --method_id="free_shipping" --user=${ADMIN_USER} -redirect_output cli wp option update --format=json woocommerce_flat_rate_1_settings '{"title":"Flat rate","tax_status":"taxable","cost":"10"}' - -echo " - Creating Cart and Checkout shortcode pages" -if ! cli wp post list --post_type=page --field=post_name | grep -q 'cart-shortcode'; then - redirect_output cli wp post create --post_type=page --post_title='Cart Shortcode' --post_name='cart-shortcode' --post_status=publish --page_template='template-fullwidth.php' --post_content='[woocommerce_cart]' -fi -if ! cli wp post list --post_type=page --field=post_name | grep -q 'checkout-shortcode'; then - redirect_output cli wp post create --post_type=page --post_title='Checkout Shortcode' --post_name='checkout-shortcode' --post_status=publish --page_template='template-fullwidth.php' --post_content='[woocommerce_checkout]' -fi - -echo " - Importing sample products" -redirect_output cli wp import wp-content/plugins/woocommerce/sample-data/sample_products.xml --authors=skip - -step "Configuring WooCommerce Gateway Stripe" -echo " - Activating plugin" -redirect_output cli wp plugin activate woocommerce-gateway-stripe - -echo " - Updating WooCommerce Gateway Stripe settings" -redirect_output cli wp option set woocommerce_stripe_settings --format=json "{\"enabled\":\"yes\",\"title\":\"Credit Card (Stripe)\",\"description\":\"Pay with your credit card via Stripe.\",\"api_credentials\":\"\",\"testmode\":\"yes\",\"test_publishable_key\":\"${STRIPE_PUB_KEY}\",\"test_secret_key\":\"${STRIPE_SECRET_KEY}\",\"publishable_key\":\"\",\"secret_key\":\"\",\"webhook\":\"\",\"test_webhook_secret\":\"\",\"webhook_secret\":\"\",\"inline_cc_form\":\"no\",\"statement_descriptor\":\"\",\"short_statement_descriptor\":\"\",\"capture\":\"yes\",\"payment_request\":\"yes\",\"payment_request_button_type\":\"buy\",\"payment_request_button_theme\":\"dark\",\"payment_request_button_locations\":[\"product\",\"cart\",\"checkout\"],\"payment_request_button_size\":\"default\",\"saved_cards\":\"yes\",\"logging\":\"no\",\"upe_checkout_experience_enabled\":\"yes\"}" - -step "Installing Woo Subscriptions" -echo " - Fetching latest version" -LATEST_RELEASE_ASSET_ID=$(curl -sH "Authorization: token $GITHUB_TOKEN" https://api.github.com/repos/woocommerce/woocommerce-subscriptions/releases/latest | jq -r '.assets[0].id') - -redirect_output curl -sLJ \ - -H "Authorization: token $GITHUB_TOKEN" \ - -H "Accept: application/octet-stream" \ - --output $E2E_ROOT/woocommerce-subscriptions.zip \ - https://api.github.com/repos/woocommerce/woocommerce-subscriptions/releases/assets/"$LATEST_RELEASE_ASSET_ID" - -redirect_output cli wp plugin install /var/www/html/wp-content/plugins/woocommerce-gateway-stripe/tests/e2e/woocommerce-subscriptions.zip --force -rm -rf $E2E_ROOT/woocommerce-subscriptions.zip - -redirect_output cli wp plugin activate woocommerce-subscriptions - -echo -echo "============================================================" -echo "WordPress => $(cli wp core version)" -echo "WooCommerce => $(cli wp plugin get woocommerce --field=version)" -echo "Stripe => $(cli wp plugin get woocommerce-gateway-stripe --field=version)" -echo "Subscriptions => $(cli wp plugin get woocommerce-subscriptions --field=version)" -echo "============================================================" -echo -step "E2E environment up and running at http://localhost:8088/wp-admin/" diff --git a/tests/e2e/bin/up.sh b/tests/e2e/bin/up.sh deleted file mode 100755 index f6b3442c8..000000000 --- a/tests/e2e/bin/up.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -set -e -. ./tests/e2e/bin/common.sh - -step "Starting E2E docker containers" -CWD="$CWD" redirect_output docker-compose -f "$E2E_ROOT/env/docker-compose.yml" up -d diff --git a/tests/e2e/bin/upload-tags.sh b/tests/e2e/bin/upload-tags.sh new file mode 100644 index 000000000..8e79a8a2f --- /dev/null +++ b/tests/e2e/bin/upload-tags.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +echo "Start uploading QIT Custom E2E tests tags"; + +# Create a temporary directory to copy tests to upload. +tmp_tests_dir="./qit-tests" +rm -rf "$tmp_tests_dir" +mkdir -p "$tmp_tests_dir" + +cp -r ./tests/e2e/. $tmp_tests_dir + +echo "Uploading default tests tag"; +# Remove the legacy tests from the default tests +rm -rf "$tmp_tests_dir/tests/_legacy-experience" +./vendor/bin/qit tag:upload woocommerce-gateway-stripe:default "$tmp_tests_dir" + +echo "Uploading legacy tests tag"; +# Only include the legacy tests +rm -rf "$tmp_tests_dir/tests/" +mkdir -p "$tmp_tests_dir/tests/_legacy-experience" +cp -r ./tests/e2e/tests/_legacy-experience/. $tmp_tests_dir/tests/_legacy-experience +./vendor/bin/qit tag:upload woocommerce-gateway-stripe:legacy "$tmp_tests_dir" + +echo "Uploading subscriptions tests tag"; +# Only include the subscriptions tests +rm -rf "$tmp_tests_dir/tests/" +mkdir -p "$tmp_tests_dir/tests/subscriptions" +cp -r ./tests/e2e/tests/subscriptions/. $tmp_tests_dir/tests/subscriptions +./vendor/bin/qit tag:upload woocommerce-gateway-stripe:subscriptions-tests "$tmp_tests_dir" + +# Remove the temporary directory +rm -rf "$tmp_tests_dir" + +echo "Finished uploading QIT Custom E2E tests tags"; diff --git a/tests/e2e/bootstrap/bootstrap.sh b/tests/e2e/bootstrap/bootstrap.sh new file mode 100644 index 000000000..647d2041d --- /dev/null +++ b/tests/e2e/bootstrap/bootstrap.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +wp rewrite structure '/%postname%/' +wp theme activate storefront +wp wc --user=admin tool run install_pages + +echo "Creating Cart and Checkout shortcode pages" +if ! wp post list --post_type=page --field=post_name | grep -q 'cart-shortcode'; then + wp post create --post_type=page --post_title='Cart Shortcode' --post_name='cart-shortcode' --post_status=publish --page_template='template-fullwidth.php' --post_content='[woocommerce_cart]' +fi +if ! wp post list --post_type=page --field=post_name | grep -q 'checkout-shortcode'; then + wp post create --post_type=page --post_title='Checkout Shortcode' --post_name='checkout-shortcode' --post_status=publish --page_template='template-fullwidth.php' --post_content='[woocommerce_checkout]' +fi + +echo "Importing sample products" +wp plugin install wordpress-importer --activate +wp import /var/www/html/wp-content/plugins/woocommerce/sample-data/sample_products.xml --authors=skip + +### TODO: Setup Stripe with Webhook, once we have a support for bootstraping using JS files +echo "Configuring WooCommerce Gateway Stripe" +echo " - Updating WooCommerce Gateway Stripe settings" +wp option set woocommerce_stripe_settings --format=json "{\"enabled\":\"yes\",\"title\":\"Credit Card (Stripe)\",\"description\":\"Pay with your credit card via Stripe.\",\"api_credentials\":\"\",\"testmode\":\"yes\",\"test_publishable_key\":\"${STRIPE_PUB_KEY}\",\"test_secret_key\":\"${STRIPE_SECRET_KEY}\",\"publishable_key\":\"\",\"secret_key\":\"\",\"webhook\":\"\",\"test_webhook_secret\":\"\",\"webhook_secret\":\"\",\"inline_cc_form\":\"no\",\"statement_descriptor\":\"\",\"short_statement_descriptor\":\"\",\"capture\":\"yes\",\"payment_request\":\"yes\",\"payment_request_button_type\":\"buy\",\"payment_request_button_theme\":\"dark\",\"payment_request_button_locations\":[\"product\",\"cart\",\"checkout\"],\"payment_request_button_size\":\"default\",\"saved_cards\":\"yes\",\"logging\":\"no\",\"upe_checkout_experience_enabled\":\"yes\"}" diff --git a/tests/e2e/bootstrap/dependencies.json b/tests/e2e/bootstrap/dependencies.json new file mode 100644 index 000000000..ebddc9420 --- /dev/null +++ b/tests/e2e/bootstrap/dependencies.json @@ -0,0 +1,5 @@ +[ + "config", + "@woocommerce/woocommerce-rest-api", + "stripe" +] diff --git a/tests/e2e/bootstrap/entrypoint.js b/tests/e2e/bootstrap/entrypoint.js new file mode 100644 index 000000000..933fdd111 --- /dev/null +++ b/tests/e2e/bootstrap/entrypoint.js @@ -0,0 +1,163 @@ +const { test, chromium } = require( '@playwright/test' ); +const fs = require( 'fs' ); +const qit = require( '/qitHelpers' ); + +/** + * Setup the test environment + * + * This is act as a Isolated setup for the stripe tests. + */ +async function setup() { + const commands = [ + 'plugin install disable-emails --activate', + 'option set woocommerce_store_address "60 29th Street"', + 'option set woocommerce_store_address_2 "#343"', + 'option set woocommerce_store_city "San Francisco"', + 'option set woocommerce_default_country "US:CA"', + 'option set woocommerce_store_postcode "94110"', + 'option set woocommerce_currency "USD"', + 'option set woocommerce_product_type "both"', + 'option set woocommerce_allow_tracking "no"', + 'option set woocommerce_coming_soon "no"', + 'theme install storefront --activate', + 'wc --user=admin tool run install_pages', + 'wc shipping_zone create --name="Everywhere" --order=1 --user=admin', + 'wc shipping_zone_method create 1 --method_id="flat_rate" --user=admin', + 'wc shipping_zone_method create 1 --method_id="free_shipping" --user=admin', + 'option update --format=json woocommerce_flat_rate_1_settings \'{"title":"Flat rate","tax_status":"taxable","cost":"10"}\'', + ]; + + for ( const command of commands ) { + await qit.wp( command ); + } + + // console.log( 'Creating Cart and Checkout shortcode pages' ); + // if ( ! ( await qit.wp( 'post list --post_type=page --field=post_name' )?.output?.includes('cart-shortcode') ) ) { + // await qit.wp('post create --post_type=page --post_title="Cart Shortcode" --post_name="cart-shortcode" --post_status=publish --page_template="template-fullwidth.php" --post_content="[woocommerce_cart]"'); + // } + + // if ( ! ( await qit.wp( 'post list --post_type=page --field=post_name' )?.output?.includes('checkout-shortcode') ) ) { + // await qit.wp('post create --post_type=page --post_title="Checkout Shortcode" --post_name="checkout-shortcode" --post_status=publish --page_template="template-fullwidth.php" --post_content="[woocommerce_checkout]"'); + // } + + // console.log( 'Importing sample products' ); + // await qit.wp('import /var/www/html/wp-content/plugins/woocommerce/sample-data/sample_products.xml --authors=skip'); + + console.log( 'Setup completed.' ); +} + +/** + * @param {import('@playwright/test').FullConfig} config + */ +test( 'Entrypoint', async ( { page }, testInfo ) => { + test.slow(); // Mark test slow to triple the default timeout. + + // Do Initial Setup + await setup(); + + // Set Stipe Credentials to qit env + qit.setEnv( 'STRIPE_PUB_KEY', process.env.STRIPE_PUB_KEY ); + qit.setEnv( 'STRIPE_SECRET_KEY', process.env.STRIPE_SECRET_KEY ); + + // Save Admin State and WC REST API Credentials + const { stateDir, baseURL, userAgent } = testInfo.project.use; + + // used throughout tests for authentication + qit.setEnv( 'ADMINSTATE', `${ stateDir }/adminState.json` ); + console.log( 'Admin state file path: ' + qit.getEnv( 'ADMINSTATE' ) ); + + // Clear out the previous save states + try { + fs.unlinkSync( qit.getEnv( 'ADMINSTATE' ) ); + console.log( 'Admin state file deleted successfully.' ); + } catch ( err ) { + if ( err.code === 'ENOENT' ) { + console.log( 'Admin state file does not exist.' ); + } else { + console.log( 'Admin state file could not be deleted: ' + err ); + } + } + + // Pre-requisites + let adminLoggedIn = false; + let customerKeyConfigured = false; + + // Specify user agent when running against an external test site to avoid getting HTTP 406 NOT ACCEPTABLE errors. + const contextOptions = { baseURL, userAgent }; + + // Create browser, browserContext, and page for customer and admin users + const browser = await chromium.launch(); + const adminContext = await browser.newContext( contextOptions ); + const adminPage = await adminContext.newPage(); + + // Sign in as admin user and save state + const adminRetries = 5; + for ( let i = 0; i < adminRetries; i++ ) { + try { + await qit.loginAsAdmin( adminPage ); + await adminPage + .context() + .storageState( { path: qit.getEnv( 'ADMINSTATE' ) } ); + console.log( 'Logged-in as admin successfully.' ); + adminLoggedIn = true; + break; + } catch ( e ) { + console.log( + `Admin log-in failed, Retrying... ${ i }/${ adminRetries }` + ); + console.log( e ); + } + } + + if ( ! adminLoggedIn ) { + console.error( + 'Cannot proceed e2e test, as admin login failed. Please check if the test site has been setup correctly.' + ); + process.exit( 1 ); + } + + // While we're here, let's add a consumer token for API access + // This step was failing occasionally, and globalsetup doesn't retry, so make it retry + const nRetries = 5; + for ( let i = 0; i < nRetries; i++ ) { + try { + console.log( 'Trying to add consumer token...' ); + await adminPage.goto( + `/wp-admin/admin.php?page=wc-settings&tab=advanced§ion=keys&create-key=1` + ); + await adminPage + .locator( '#key_description' ) + .fill( 'Key for API access' ); + await adminPage + .locator( '#key_permissions' ) + .selectOption( 'read_write' ); + await adminPage.locator( 'text=Generate API key' ).click(); + qit.setEnv( + 'CONSUMER_KEY', + await adminPage.locator( '#key_consumer_key' ).inputValue() + ); + qit.setEnv( + 'CONSUMER_SECRET', + await adminPage.locator( '#key_consumer_secret' ).inputValue() + ); + console.log( 'Added consumer token successfully.' ); + customerKeyConfigured = true; + break; + } catch ( e ) { + console.log( + `Failed to add consumer token. Retrying... ${ i }/${ nRetries }` + ); + console.log( e ); + } + } + + if ( ! customerKeyConfigured ) { + console.error( + 'Cannot proceed e2e test, as we could not set the customer key. Please check if the test site has been setup correctly.' + ); + process.exit( 1 ); + } + + await adminContext.close(); + await browser.close(); +} ); diff --git a/tests/e2e/test-data/default.json b/tests/e2e/config/default.json similarity index 99% rename from tests/e2e/test-data/default.json rename to tests/e2e/config/default.json index c7a96984e..3815edf28 100644 --- a/tests/e2e/test-data/default.json +++ b/tests/e2e/config/default.json @@ -2,7 +2,7 @@ "users": { "admin": { "username": "admin", - "password": "admin", + "password": "password", "email": "admin@e2etestsuite.com" }, "customer": { diff --git a/tests/e2e/config/global-setup-docker.js b/tests/e2e/config/global-setup-docker.js deleted file mode 100644 index 4e21ce2d2..000000000 --- a/tests/e2e/config/global-setup-docker.js +++ /dev/null @@ -1,89 +0,0 @@ -import * as dotenv from 'dotenv'; -import { chromium } from '@playwright/test'; -import fs from 'fs'; - -import { - loginAdminAndSaveState, - createApiTokens, -} from '../utils/playwright-setup'; - -dotenv.config( { - path: `${ process.env.E2E_ROOT }/config/local.env`, -} ); - -const { - ADMIN_USER, - ADMIN_PASSWORD, - STRIPE_SETUP, - STRIPE_PUB_KEY, - STRIPE_SECRET_KEY, -} = process.env; - -export default async function ( config ) { - console.time( 'Total Setup Time' ); - const { baseURL, stateDir, userAgent } = config.projects[ 0 ].use; - - if ( STRIPE_SETUP && ( ! STRIPE_PUB_KEY || ! STRIPE_SECRET_KEY ) ) { - console.error( - 'The Stripe setup needs that the STRIPE_PUB_KEY and the STRIPE_SECRET_KEY secrets are set in your local.env file.' - ); - process.exit( 1 ); - } - - console.log( `BASE_URL = ${ baseURL }\n` ); - - // used throughout tests for authentication - process.env.ADMINSTATE = `${ stateDir }/adminState.json`; - - // Clear out the previous saved states - try { - fs.unlinkSync( process.env.ADMINSTATE ); - } catch ( err ) { - if ( err.code !== 'ENOENT' ) { - console.log( 'Admin state file could not be deleted: ' + err ); - } - } - - let adminSetupReady = false; - - // Specify user agent when running against an external test site to avoid getting HTTP 406 NOT ACCEPTABLE errors. - const contextOptions = { baseURL, userAgent }; - - // Create browser, browserContext, and page for customer and admin users - const browser = await chromium.launch(); - const adminContext = await browser.newContext( contextOptions ); - const adminPage = await adminContext.newPage(); - - try { - await loginAdminAndSaveState( { - page: adminPage, - username: ADMIN_USER, - password: ADMIN_PASSWORD, - statePath: process.env.ADMINSTATE, - retries: 1, - } ); - } catch ( err ) { - console.error( err ); - console.error( - 'Admin login failed. Please check if the test site has been setup correctly.' - ); - process.exit( 1 ); - } - - const apiTokensPage = await adminContext.newPage(); - - try { - await createApiTokens( apiTokensPage ); - } catch ( err ) { - console.error( - 'Could not create a WC REST API key. Please check if the test site has been setup correctly.' - ); - process.exit( 1 ); - } - - await adminContext.close(); - await browser.close(); - - console.timeEnd( 'Total Setup Time' ); - console.log( `\n======\n\n` ); -} diff --git a/tests/e2e/config/global-setup.js b/tests/e2e/config/global-setup.js deleted file mode 100644 index 1dcfdf115..000000000 --- a/tests/e2e/config/global-setup.js +++ /dev/null @@ -1,237 +0,0 @@ -import * as dotenv from 'dotenv'; -import { chromium } from '@playwright/test'; -import fs from 'fs'; - -import { - loginAdminAndSaveState, - createApiTokens, - installPluginFromRepository, - setupWoo, - setupStripe, - installWooSubscriptionsFromRepo, - checkWooGutenbergProductsBlockVersion, -} from '../utils/playwright-setup'; - -dotenv.config( { - path: `${ process.env.E2E_ROOT }/config/local.env`, -} ); - -const { - BASE_URL, - ADMIN_USER, - ADMIN_PASSWORD, - PLUGIN_VERSION, - WOO_SETUP, - STRIPE_SETUP, - STRIPE_PUB_KEY, - STRIPE_SECRET_KEY, - SSH_HOST, - SSH_USER, - SSH_PASSWORD, - SSH_PATH, - GITHUB_TOKEN, -} = process.env; - -function wait( milliseconds ) { - return new Promise( ( resolve ) => { - setTimeout( resolve, milliseconds ); - } ); -} - -module.exports = async ( config ) => { - console.time( 'Total Setup Time' ); - const { stateDir, baseURL, userAgent } = config.projects[ 0 ].use; - - // Validate env variables are present. - if ( ! BASE_URL ) { - console.error( 'The --base_url parameter is mandatory.' ); - process.exit( 1 ); - } - - if ( ! ADMIN_USER || ! ADMIN_PASSWORD ) { - console.error( - 'Cannot proceed e2e test, ADMIN_USER and ADMIN_PASSWORD secrets are not set. Please check your local.env file.' - ); - process.exit( 1 ); - } - - if ( - WOO_SETUP && - ( ! SSH_HOST || ! SSH_USER || ! SSH_PASSWORD || ! SSH_PATH ) - ) { - console.error( - 'The WooCommerce setup needs SSH credentials (SSH_HOST, SSH_USER, SSH_PASSWORD, SSH_PATH) in your local.env file.' - ); - process.exit( 1 ); - } - - if ( STRIPE_SETUP && ( ! STRIPE_PUB_KEY || ! STRIPE_SECRET_KEY ) ) { - console.error( - 'The Stripe setup needs that the STRIPE_PUB_KEY and the STRIPE_SECRET_KEY secrets are set in your local.env file.' - ); - process.exit( 1 ); - } - - console.log( `Base URL: ${ baseURL }` ); - if ( PLUGIN_VERSION ) { - console.log( `Plugin Version: ${ PLUGIN_VERSION }` ); - } - console.log( `\n======\n` ); - - // used throughout tests for authentication - process.env.ADMINSTATE = `${ stateDir }adminState.json`; - - // Clear out the previous saved states - try { - fs.unlinkSync( process.env.ADMINSTATE ); - console.log( 'Admin state file deleted successfully.' ); - } catch ( err ) { - if ( err.code === 'ENOENT' ) { - console.log( 'Admin state file does not exist.' ); - } else { - console.log( 'Admin state file could not be deleted: ' + err ); - } - } - - // Setup WooCommerce before any browser interaction. - if ( WOO_SETUP ) { - await setupWoo().catch( ( e ) => { - console.error( e ); - console.error( - 'Cannot proceed e2e test, as we could not update the plugin. Please check if the test site has been setup correctly.' - ); - process.exit( 1 ); - } ); - } else { - console.log( 'Skipping Woo Setup.' ); - } - - let adminSetupReady = false; - - // Specify user agent when running against an external test site to avoid getting HTTP 406 NOT ACCEPTABLE errors. - const contextOptions = { baseURL, userAgent }; - - // Create browser, browserContext, and page for customer and admin users - const browser = await chromium.launch(); - const adminContext = await browser.newContext( contextOptions ); - const adminPage = await adminContext.newPage(); - - loginAdminAndSaveState( { - page: adminPage, - username: ADMIN_USER, - password: ADMIN_PASSWORD, - statePath: process.env.ADMINSTATE, - retries: 5, - } ) - .then( async () => { - const apiTokensPage = await adminContext.newPage(); - const updatePluginPage = await adminContext.newPage(); - const wooSubscriptionsInstallPage = await adminContext.newPage(); - - // create consumer token and update plugin in parallel. - let restApiKeysFinished = false; - let pluginUpdateFinished = false; - let wooSubscriptionsInstallFinished = false; - let stripeSetupFinished = false; - - createApiTokens( apiTokensPage ) - .then( () => { - restApiKeysFinished = true; - } ) - .catch( () => { - console.error( - 'Cannot proceed e2e test, as we could not create a WC REST API key. Please check if the test site has been setup correctly.' - ); - process.exit( 1 ); - } ); - - if ( PLUGIN_VERSION ) { - installPluginFromRepository( updatePluginPage ) - .then( () => { - pluginUpdateFinished = true; - } ) - .catch( ( e ) => { - console.error( e ); - console.error( - 'Cannot proceed e2e test, as we could not update the plugin. Please check if the test site has been setup correctly.' - ); - process.exit( 1 ); - } ); - } else { - console.log( - 'Skipping plugin update. The version already installed on the test site will be used.' - ); - pluginUpdateFinished = true; - } - - if ( WOO_SETUP && GITHUB_TOKEN ) { - installWooSubscriptionsFromRepo( wooSubscriptionsInstallPage ) - .then( () => { - wooSubscriptionsInstallFinished = true; - } ) - .catch( ( e ) => { - console.error( e ); - console.error( - 'Cannot proceed e2e test, as we could not install WooCommerce Subscriptions. Please check if the GITHUB_TOKEN env variable is valid.' - ); - process.exit( 1 ); - } ); - } else { - console.log( - 'Skipping WC Subscriptions setup. The version already installed on the test site will be used if needed.' - ); - wooSubscriptionsInstallFinished = true; - } - - if ( STRIPE_SETUP ) { - while ( PLUGIN_VERSION && ! pluginUpdateFinished ) { - await wait( 1000 ); - } - - setupStripe( adminPage, baseURL ) - .then( () => { - stripeSetupFinished = true; - } ) - .catch( ( e ) => { - console.error( e ); - console.error( - 'Cannot proceed e2e test, as we could not setup Stripe keys in the plugin. Please check if the test site has been setup correctly.' - ); - process.exit( 1 ); - } ); - } else { - console.log( - 'Skipping Stripe setup. Ensure Stripe webhook and keys are already setup in this environment.' - ); - stripeSetupFinished = true; - } - - while ( - ! pluginUpdateFinished || - ! restApiKeysFinished || - ! stripeSetupFinished || - ! wooSubscriptionsInstallFinished - ) { - await wait( 1000 ); - } - - adminSetupReady = true; - } ) - .catch( ( e ) => { - console.error( e ); - console.error( - 'Cannot proceed e2e test, as admin login failed. Please check if the test site has been setup correctly.' - ); - process.exit( 1 ); - } ); - - while ( ! adminSetupReady ) { - await wait( 1000 ); - } - - await adminContext.close(); - await browser.close(); - - console.timeEnd( 'Total Setup Time' ); - console.log( `\n======\n\n` ); -}; diff --git a/tests/e2e/config/global-teardown.js b/tests/e2e/config/global-teardown.js deleted file mode 100644 index 0b841a839..000000000 --- a/tests/e2e/config/global-teardown.js +++ /dev/null @@ -1,58 +0,0 @@ -import path from 'path'; -import fs from 'fs'; - -import { chromium } from '@playwright/test'; -import { user } from '../utils'; - -const { ADMIN_USER, ADMIN_PASSWORD, PLUGIN_VERSION } = process.env; - -module.exports = async ( config ) => { - const { baseURL, userAgent } = config.projects[ 0 ].use; - - console.log( `\n======\n` ); - - // Specify user agent when running against an external test site to avoid getting HTTP 406 NOT ACCEPTABLE errors. - const contextOptions = { baseURL, userAgent }; - - const browser = await chromium.launch(); - const context = await browser.newContext( contextOptions ); - const adminPage = await context.newPage(); - - let consumerTokenCleared = false; - - await user.login( adminPage, ADMIN_USER, ADMIN_PASSWORD ); - - // Clean up the consumer keys - const keysRetries = 5; - for ( let i = 1; i <= keysRetries; i++ ) { - try { - console.log( '- Trying to clear consumer token... Try:' + i ); - - await adminPage.goto( - `/wp-admin/admin.php?page=wc-settings&tab=advanced§ion=keys` - ); - await adminPage.dispatchEvent( 'a.submitdelete', 'click' ); - console.log( '\u2714 Cleared up consumer token successfully.' ); - consumerTokenCleared = true; - break; - } catch ( e ) { - console.error( - `Failed to clear consumer token. Retrying... ${ i }/${ keysRetries }. Error:`, - e - ); - } - } - - if ( ! consumerTokenCleared ) { - console.error( 'Could not clear consumer token.' ); - process.exit( 1 ); - } - - // Delete the tmp folder. - fs.rmSync( path.resolve( __dirname, `../../tmp` ), { - recursive: true, - force: true, - } ); - - console.log( `\n======\n` ); -}; diff --git a/tests/e2e/config/local.env.example b/tests/e2e/config/local.env.example index de30de0ad..5df6d1dd6 100644 --- a/tests/e2e/config/local.env.example +++ b/tests/e2e/config/local.env.example @@ -1,16 +1,3 @@ -# GitHub token with access to Woo Subscriptions repository -GITHUB_TOKEN= - -# The WP Admin credentials -ADMIN_USER=demo -ADMIN_PASSWORD= - -# Test site SSH credentials. WP CLI must be installed on it. -SSH_HOST=ssh.atomicsites.net -SSH_USER= -SSH_PASSWORD= -SSH_PATH=/srv/htdocs/ - # Stripe keys (TEST MODE). # IMPORTANT: Please only use API keys from your personal Stripe account when running E2E tests. STRIPE_PUB_KEY= diff --git a/tests/e2e/config/playwright.config.js b/tests/e2e/config/playwright.config.js deleted file mode 100644 index efcbbf7ed..000000000 --- a/tests/e2e/config/playwright.config.js +++ /dev/null @@ -1,101 +0,0 @@ -'use strict'; - -/* jshint node: true */ - -import { devices } from '@playwright/test'; -import dotenv from 'dotenv'; - -dotenv.config( { - path: `${ process.env.E2E_ROOT }/config/local.env`, -} ); - -const { BASE_URL, CI, DOCKER, E2E_MAX_FAILURES, TIMEOUT } = process.env; - -const config = { - globalSetup: DOCKER ? './global-setup-docker' : './global-setup', - globalTeardown: './global-teardown', - - testDir: '../tests', - - // Maximum time one test can run for - timeout: TIMEOUT ? Number( TIMEOUT ) : 90 * 1000, - - expect: { - // Maximum time expect() should wait for the condition to be met - // For example in `await expect(locator).toHaveText();` - timeout: 20 * 1000, - }, - - // Folder for test artifacts such as screenshots, videos, traces, etc - outputDir: '../test-results/output', - - /* Retry on CI only */ - retries: CI ? 3 : 0, - - workers: 5, - - // Reporter to use. See https://playwright.dev/docs/test-reporters - reporter: [ - [ CI ? 'github' : 'list' ], - [ - 'html', - { - outputFolder: '../test-results/report-html', - open: CI ? 'never' : 'on-failure', - }, - ], - [ - 'allure-playwright', - { - outputFolder: 'tests/e2e/test-results/report-allure/', - }, - ], - ], - - maxFailures: E2E_MAX_FAILURES ? Number( E2E_MAX_FAILURES ) : 0, - - use: { - baseURL: BASE_URL, - - stateDir: 'tests/e2e/test-results/storage/', - - // Capture screenshot after each test failure - screenshot: 'only-on-failure', - - // Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer - trace: 'retain-on-failure', - - // Record video only when retrying a test for the first time - video: 'on-first-retry', - - viewport: { width: 1280, height: 720 }, - }, - - projects: [ - { - name: 'default-setup', - testMatch: '/default.setup.js', - use: { ...devices[ 'Desktop Chrome' ] }, - }, - { - name: 'default', - testMatch: '**/*.spec.js', - testIgnore: /_legacy-experience/, - dependencies: [ 'default-setup' ], - use: { ...devices[ 'Desktop Chrome' ] }, - }, - { - name: 'legacy-setup', - testMatch: '_legacy-experience/legacy.setup.js', - use: { ...devices[ 'Desktop Chrome' ] }, - }, - { - name: 'legacy', - testMatch: '/_legacy-experience/**/*.spec.js', - dependencies: [ 'legacy-setup' ], - use: { ...devices[ 'Desktop Chrome' ] }, - }, - ], -}; - -export default config; diff --git a/tests/e2e/env/Dockerfile b/tests/e2e/env/Dockerfile deleted file mode 100644 index 5800e8862..000000000 --- a/tests/e2e/env/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM wordpress:php7.4 -RUN apt-get update \ - && apt-get install --assume-yes --quiet --no-install-recommends gnupg2 subversion mariadb-client less jq -RUN apt-get install -y openssh-client -RUN curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar \ - && chmod +x wp-cli.phar \ - && mv wp-cli.phar /usr/local/bin/wp diff --git a/tests/e2e/env/default.env b/tests/e2e/env/default.env deleted file mode 100644 index 205279cc0..000000000 --- a/tests/e2e/env/default.env +++ /dev/null @@ -1,19 +0,0 @@ -# WordPress -WORDPRESS_DB_HOST=db:3306 -WORDPRESS_DB_USER=wordpress -WORDPRESS_DB_PASSWORD=wordpress -WORDPRESS_DB_NAME=wordpress -WORDPRESS_DEBUG=1 -ABSPATH=/usr/src/wordpress/ - -# This is needed to set the HOME directory WP-CLI tries to write to `/etc/X11/fs/.wp-cli/cache/` -# according to https://github.com/docker-library/docs/pull/1539/files. -HOME=/tmp - -# Database -MYSQL_ROOT_PASSWORD=wordpress -MYSQL_DATABASE=wordpress -MYSQL_USER=wordpress -MYSQL_PASSWORD=wordpress - -WCSTRIPE_DIR=/var/www/html/wp-content/plugins/woocommerce-gateway-stripe diff --git a/tests/e2e/env/docker-compose.yml b/tests/e2e/env/docker-compose.yml deleted file mode 100644 index 530aee261..000000000 --- a/tests/e2e/env/docker-compose.yml +++ /dev/null @@ -1,33 +0,0 @@ -volumes: - # Kludge for not having the ./docker directory bound recursively - dockerdirectory: - -services: - wordpress: - build: . - image: wordpress - container_name: wcstripe-e2e-wordpress - depends_on: - - db - links: - - db:mysql - ports: - - "8088:80" - env_file: - - default.env - volumes: - - ./docker/wordpress:/var/www/html/ - - ./docker/logs/apache2/:/var/log/apache2 - - ${CWD}:/var/www/html/wp-content/plugins/woocommerce-gateway-stripe - - dockerdirectory:/var/www/html/wp-content/plugins/woocommerce-gateway-stripe/tests/e2e/env/docker - extra_hosts: - - "host.docker.internal:host-gateway" - db: - container_name: wcstripe-e2e-mysql - image: mariadb:10.5.8 - ports: - - "6789:3306" - env_file: - - default.env - volumes: - - ./docker/data:/var/lib/mysql diff --git a/tests/e2e/env/e2e.sh b/tests/e2e/env/e2e.sh deleted file mode 100755 index cd2595bb8..000000000 --- a/tests/e2e/env/e2e.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env bash - -set -e - -. ./tests/e2e/env/shared.sh - -common_env="NODE_CONFIG_DIR='tests/e2e/test-data'" -test_env="$common_env" - -accepted_args=("--base_url" "--version", "--with_woo_setup", "--with_stripe_setup") -additional_args="" -for arg in "$@"; do - - key=$(echo $arg | cut -f1 -d=) - value=$(echo $arg | cut -f2 -d=) - - # If it's one of the expected parameters, save it in a variable. - if [[ ${accepted_args[*]} =~ "${key}" ]]; then - v="${key/--/}" - declare $v="${value}" - else - # concatenate to pass along to Playwright - additional_args="$additional_args $arg" - fi - -done - -# Remove the trailing slash from the URL. -base_url=$(echo "$base_url" | sed 's:/*$::') - -# Flag for Jurassic Ninja sites. Can be used to set up the site differently. -if [[ $base_url == *".jurassic.ninja" ]]; then - test_env="$test_env IS_JURASSIC='1'" -fi - -if [[ $base_url != "" ]]; then - test_env="$test_env BASE_URL='${base_url}'" -fi - -if [[ $version != "" ]]; then - test_env="$test_env PLUGIN_VERSION='${version}'" -fi - -if [[ $with_woo_setup != "" ]]; then - test_env="$test_env WOO_SETUP='1'" -fi - -if [[ $with_stripe_setup != "" ]]; then - test_env="$test_env STRIPE_SETUP='1'" -fi - -cross-env $test_env playwright test --config=tests/e2e/config/playwright.config.js $additional_args diff --git a/tests/e2e/env/shared.sh b/tests/e2e/env/shared.sh deleted file mode 100644 index 3155b4b87..000000000 --- a/tests/e2e/env/shared.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -# Init variables and functions used in other scripts -cwd=$(pwd) -export E2E_ROOT="$cwd/tests/e2e" -export PLUGIN_REPOSITORY="woocommerce/woocommerce-gateway-stripe" \ No newline at end of file diff --git a/tests/e2e/test-data/cart-block-content.html b/tests/e2e/test-data/cart-block-content.html deleted file mode 100644 index 671eb1b68..000000000 --- a/tests/e2e/test-data/cart-block-content.html +++ /dev/null @@ -1,87 +0,0 @@ - -
-
-
-
- - - -
-

You may be interested in…

- - - -
-
-
- - - -
-
-
- - - -
- - - -
- - - -
- - - -
- - - -
- - - -
-
- - - -
- - - -
- - - -
-
-
- - - -
-
- - - -

Your cart is currently empty!

- - - -

Browse store.

- - - -
- - - -

New in store

- - -
-
- \ No newline at end of file diff --git a/tests/e2e/test-data/cart-shortcode-content.html b/tests/e2e/test-data/cart-shortcode-content.html deleted file mode 100644 index 0e1101b3a..000000000 --- a/tests/e2e/test-data/cart-shortcode-content.html +++ /dev/null @@ -1 +0,0 @@ -[woocommerce_cart] diff --git a/tests/e2e/test-data/checkout-block-content.html b/tests/e2e/test-data/checkout-block-content.html deleted file mode 100644 index 1480b8f69..000000000 --- a/tests/e2e/test-data/checkout-block-content.html +++ /dev/null @@ -1,71 +0,0 @@ - -
-
-
- - - -
- - - -
- - - -
- - - -
- - - -
- - - -
- - - -
- - - -
-
- - - -
-
-
- - - -
- - - -
- - - -
- - - -
- - - -
- - - -
-
-
-
- \ No newline at end of file diff --git a/tests/e2e/test-data/checkout-shortcode-content.html b/tests/e2e/test-data/checkout-shortcode-content.html deleted file mode 100644 index 41be7ea22..000000000 --- a/tests/e2e/test-data/checkout-shortcode-content.html +++ /dev/null @@ -1 +0,0 @@ -[woocommerce_checkout] diff --git a/tests/e2e/tests/default.setup.js b/tests/e2e/tests/_default.setup.spec.js similarity index 88% rename from tests/e2e/tests/default.setup.js rename to tests/e2e/tests/_default.setup.spec.js index bd148c50b..b6326f1f9 100644 --- a/tests/e2e/tests/default.setup.js +++ b/tests/e2e/tests/_default.setup.spec.js @@ -1,12 +1,9 @@ -'use strict'; - -/* jshint node: true */ - import { expect, test as setup } from '@playwright/test'; +import qit from '/qitHelpers'; setup( 'Disable legacy checkout experience', async ( { browser } ) => { const adminContext = await browser.newContext( { - storageState: process.env.ADMINSTATE, + storageState: qit.getEnv( 'ADMINSTATE' ), } ); const page = await adminContext.newPage(); diff --git a/tests/e2e/tests/_legacy-experience/legacy.setup.js b/tests/e2e/tests/_legacy-experience/_legacy.setup.spec.js similarity index 79% rename from tests/e2e/tests/_legacy-experience/legacy.setup.js rename to tests/e2e/tests/_legacy-experience/_legacy.setup.spec.js index 535b7a22c..bf6181fb8 100644 --- a/tests/e2e/tests/_legacy-experience/legacy.setup.js +++ b/tests/e2e/tests/_legacy-experience/_legacy.setup.spec.js @@ -1,12 +1,13 @@ 'use strict'; /* jshint node: true */ +import qit from '/qitHelpers'; import { expect, test as setup } from '@playwright/test'; -setup( 'Enable legacy checkout experience', async ( { browser } ) => { +setup( 'Enable legacy checkout experience @legacy', async ( { browser } ) => { const adminContext = await browser.newContext( { - storageState: process.env.ADMINSTATE, + storageState: qit.getEnv( 'ADMINSTATE' ), } ); const page = await adminContext.newPage(); diff --git a/tests/e2e/tests/_legacy-experience/checkout/card-failures.spec.js b/tests/e2e/tests/_legacy-experience/checkout/card-failures.spec.js index d36afd71f..e77328779 100644 --- a/tests/e2e/tests/_legacy-experience/checkout/card-failures.spec.js +++ b/tests/e2e/tests/_legacy-experience/checkout/card-failures.spec.js @@ -1,6 +1,5 @@ import { test, expect } from '@playwright/test'; -import config from 'config'; -import { payments } from '../../../utils'; +import { payments, config } from '../../../utils'; const { emptyCart, @@ -22,7 +21,7 @@ const testCard = async ( page, cardKey ) => { const card = config.get( cardKey ); await fillCreditCardDetailsShortcodeLegacy( page, card ); - await page.locator( 'text=Place order' ).click(); + await page.locator( '#place_order' ).first().click(); expect .soft( await page.innerText( '.woocommerce-error' ) ) @@ -33,7 +32,7 @@ const testCardBlocks = async ( page, cardKey ) => { const card = config.get( cardKey ); await fillCreditCardDetailsShortcodeLegacy( page, card ); - await page.locator( 'text=Place order' ).click(); + await page.locator( '#place_order' ).first().click(); expect .soft( @@ -44,8 +43,9 @@ const testCardBlocks = async ( page, cardKey ) => { .toMatch( new RegExp( `(?:${ card.error.join( '|' ) })`, 'i' ) ); }; +test.slow(); test.describe.configure( { mode: 'parallel' } ); -test.describe( 'customer cannot checkout with invalid cards', () => { +test.describe( 'customer cannot checkout with invalid cards @legacy', () => { test( `a declined card shows the correct error message @smoke`, async ( { page, } ) => testCard( page, 'cards.declined' ) ); diff --git a/tests/e2e/tests/_legacy-experience/checkout/normal-card.spec.js b/tests/e2e/tests/_legacy-experience/checkout/normal-card.spec.js index b38273be9..f7da8ddcc 100644 --- a/tests/e2e/tests/_legacy-experience/checkout/normal-card.spec.js +++ b/tests/e2e/tests/_legacy-experience/checkout/normal-card.spec.js @@ -1,6 +1,5 @@ import { test, expect } from '@playwright/test'; -import config from 'config'; -import { payments } from '../../../utils'; +import { payments, config } from '../../../utils'; const { emptyCart, @@ -9,7 +8,8 @@ const { fillCreditCardDetailsShortcodeLegacy, } = payments; -test( 'customer can checkout with a normal credit card @smoke', async ( { +test.slow(); +test( 'customer can checkout with a normal credit card @smoke @legacy', async ( { page, } ) => { await emptyCart( page ); @@ -22,8 +22,10 @@ test( 'customer can checkout with a normal credit card @smoke', async ( { page, config.get( 'cards.basic' ) ); - await page.locator( 'text=Place order' ).click(); - await page.waitForNavigation(); + await page.locator( '#place_order' ).first().click(); + await page.waitForURL( '**/checkout/order-received/**', { + timeout: 20000, + } ); // Allow some extra time for the redirect to complete. await expect( page.locator( 'h1.entry-title' ) ).toHaveText( 'Order received' diff --git a/tests/e2e/tests/_legacy-experience/checkout/saved-card.spec.js b/tests/e2e/tests/_legacy-experience/checkout/saved-card.spec.js index d7145bf1a..ef75c65bd 100644 --- a/tests/e2e/tests/_legacy-experience/checkout/saved-card.spec.js +++ b/tests/e2e/tests/_legacy-experience/checkout/saved-card.spec.js @@ -1,6 +1,6 @@ import { test, expect } from '@playwright/test'; -import config from 'config'; -import { payments, api, user } from '../../../utils'; +import { payments, api, config } from '../../../utils'; +import qit from '/qitHelpers'; const { emptyCart, @@ -10,7 +10,7 @@ const { } = payments; let username, userEmail; - +test.slow(); test.beforeAll( async () => { // This allow multiple tests to run in parallel. const randomString = Date.now(); @@ -27,9 +27,11 @@ test.beforeAll( async () => { await api.create.customer( user ); } ); -test( 'customer can checkout with a saved card @smoke', async ( { page } ) => { +test( 'customer can checkout with a saved card @smoke @legacy', async ( { + page, +} ) => { await test.step( 'customer login', async () => { - await user.login( + await qit.loginAs( page, username, config.get( 'users.customer.password' ) @@ -48,9 +50,11 @@ test( 'customer can checkout with a saved card @smoke', async ( { page } ) => { // check box to save payment method. await page.locator( '#wc-stripe-new-payment-method' ).click(); - await page.locator( 'text=Place order' ).click(); + await page.locator( '#place_order' ).first().click(); - await page.waitForNavigation(); + await page.waitForURL( '**/checkout/order-received/**', { + timeout: 20000, + } ); // Allow some extra time for the redirect to complete. await expect( page.locator( 'h1.entry-title' ) ).toHaveText( 'Order received' ); @@ -68,9 +72,11 @@ test( 'customer can checkout with a saved card @smoke', async ( { page } ) => { ) ).toHaveCount( 1 ); - await page.locator( 'text=Place order' ).click(); + await page.locator( '#place_order' ).first().click(); - await page.waitForNavigation(); + await page.waitForURL( '**/checkout/order-received/**', { + timeout: 20000, + } ); // Allow some extra time for the redirect to complete. await expect( page.locator( 'h1.entry-title' ) ).toHaveText( 'Order received' ); diff --git a/tests/e2e/tests/_legacy-experience/checkout/sca-card.spec.js b/tests/e2e/tests/_legacy-experience/checkout/sca-card.spec.js index e12bf49e1..dbb2304da 100644 --- a/tests/e2e/tests/_legacy-experience/checkout/sca-card.spec.js +++ b/tests/e2e/tests/_legacy-experience/checkout/sca-card.spec.js @@ -1,6 +1,5 @@ import { test, expect } from '@playwright/test'; -import config from 'config'; -import { payments } from '../../../utils'; +import { payments, config } from '../../../utils'; const { emptyCart, @@ -8,8 +7,12 @@ const { setupShortcodeCheckout, fillCreditCardDetailsShortcodeLegacy, } = payments; +test.slow(); +test( 'customer can checkout with a SCA card @smoke @legacy', async ( { + page, +} ) => { + test.slow(); // Make sure test has enough time to complete. -test( 'customer can checkout with a SCA card @smoke', async ( { page } ) => { await emptyCart( page ); await setupCart( page ); await setupShortcodeCheckout( @@ -20,7 +23,7 @@ test( 'customer can checkout with a SCA card @smoke', async ( { page } ) => { page, config.get( 'cards.3ds' ) ); - await page.locator( 'text=Place order' ).click(); + await page.locator( '#place_order' ).first().click(); // Wait until the SCA frame is available while ( @@ -31,7 +34,7 @@ test( 'customer can checkout with a SCA card @smoke', async ( { page } ) => { await page.waitForTimeout( 1000 ); } // Not ideal, but the iframe body gets repalced after load, so a waitFor does not work here. - await page.waitForTimeout( 2000 ); + await page.waitForTimeout( 5000 ); await page .frame( { diff --git a/tests/e2e/tests/_legacy-experience/checkout/subscription-product.spec.js b/tests/e2e/tests/_legacy-experience/checkout/subscription-product.spec.js index b74ff2a85..1f76141ae 100644 --- a/tests/e2e/tests/_legacy-experience/checkout/subscription-product.spec.js +++ b/tests/e2e/tests/_legacy-experience/checkout/subscription-product.spec.js @@ -1,6 +1,5 @@ import { test, expect } from '@playwright/test'; -import config from 'config'; -import { payments, api } from '../../../utils'; +import { payments, api, config } from '../../../utils'; const { setupShortcodeCheckout, @@ -8,7 +7,7 @@ const { } = payments; let productId; - +test.slow(); test.beforeAll( async () => { const product = { ...config.get( 'products.subscription' ), @@ -32,7 +31,7 @@ test.afterAll( async () => { await api.deletePost.product( productId ); } ); -test( 'customer can purchase a subscription product @smoke @subscriptions', async ( { +test( 'customer can purchase a subscription product @smoke @subscriptions @legacy', async ( { page, } ) => { await page.goto( `?p=${ productId }` ); @@ -51,8 +50,10 @@ test( 'customer can purchase a subscription product @smoke @subscriptions', asyn config.get( 'cards.basic' ) ); - await page.locator( 'text=Sign up now' ).click(); - await page.waitForNavigation(); + await page.locator( 'text=Sign up now' ).first().click(); + await page.waitForURL( '**/checkout/order-received/**', { + timeout: 20000, + } ); // Allow some extra time for the redirect to complete. await expect( page.locator( 'h1.entry-title' ) ).toHaveText( 'Order received' diff --git a/tests/e2e/tests/_legacy-experience/order/full-refund.spec.js b/tests/e2e/tests/_legacy-experience/order/full-refund.spec.js index aac49c180..055136b24 100644 --- a/tests/e2e/tests/_legacy-experience/order/full-refund.spec.js +++ b/tests/e2e/tests/_legacy-experience/order/full-refund.spec.js @@ -1,7 +1,7 @@ import stripe from 'stripe'; import { test, expect } from '@playwright/test'; -import config from 'config'; -import { api, payments } from '../../../utils'; +import { api, payments, config } from '../../../utils'; +import qit from '/qitHelpers'; const { emptyCart, @@ -10,11 +10,14 @@ const { fillCreditCardDetailsShortcodeLegacy, } = payments; -test( 'merchant can issue a full refund @smoke', async ( { browser } ) => { +test.slow(); +test( 'merchant can issue a full refund @smoke @legacy', async ( { + browser, +} ) => { let orderId, stripeChargeId, stripeRefundId; const adminContext = await browser.newContext( { - storageState: process.env.ADMINSTATE, + storageState: qit.getEnv( 'ADMINSTATE' ), } ); const adminPage = await adminContext.newPage(); @@ -33,8 +36,10 @@ test( 'merchant can issue a full refund @smoke', async ( { browser } ) => { userPage, config.get( 'cards.basic' ) ); - await userPage.locator( 'text=Place order' ).click(); - await userPage.waitForNavigation(); + await userPage.locator( '#place_order' ).first().click(); + await userPage.waitForURL( '**/checkout/order-received/**', { + timeout: 20000, + } ); // Allow some extra time for the redirect to complete. await expect( userPage.locator( 'h1.entry-title' ) ).toHaveText( 'Order received' @@ -101,7 +106,7 @@ test( 'merchant can issue a full refund @smoke', async ( { browser } ) => { ); await test.step( 'check Stripe payment status ', async () => { - const stripeClient = stripe( process.env.STRIPE_SECRET_KEY ); + const stripeClient = stripe( qit.getEnv( 'STRIPE_SECRET_KEY' ) ); const charge = await stripeClient.charges.retrieve( stripeChargeId, { expand: [ 'refunds' ], diff --git a/tests/e2e/tests/_legacy-experience/subscriptions/subscription-renewal.spec.js b/tests/e2e/tests/_legacy-experience/subscriptions/subscription-renewal.spec.js index d9ae7302a..c5456ea5c 100644 --- a/tests/e2e/tests/_legacy-experience/subscriptions/subscription-renewal.spec.js +++ b/tests/e2e/tests/_legacy-experience/subscriptions/subscription-renewal.spec.js @@ -1,6 +1,6 @@ import { test, expect } from '@playwright/test'; -import config from 'config'; -import { payments, api, user } from '../../../utils'; +import { payments, api, config } from '../../../utils'; +import qit from '/qitHelpers'; const { setupShortcodeCheckout, @@ -47,11 +47,12 @@ test.afterAll( async () => { await api.deletePost.product( productId ); } ); -test( 'customer can renew a subscription @smoke @subscriptions', async ( { +test.slow(); +test( 'customer can renew a subscription @smoke @subscriptions @legacy', async ( { page, } ) => { await test.step( 'customer login', async () => { - await user.login( + await qit.loginAs( page, username, config.get( 'users.customer.password' ) @@ -68,7 +69,7 @@ test( 'customer can renew a subscription @smoke @subscriptions', async ( { config.get( 'cards.basic' ) ); - await page.locator( 'text=Sign up now' ).click(); + await page.locator( 'text=Sign up now' ).first().click(); await expect( page.locator( 'h1.entry-title' ) ).toHaveText( 'Order received' diff --git a/tests/e2e/tests/_legacy-experience/woocommerce-blocks/card-failures.spec.js b/tests/e2e/tests/_legacy-experience/woocommerce-blocks/card-failures.spec.js index 33d281c9e..166b1c1ca 100644 --- a/tests/e2e/tests/_legacy-experience/woocommerce-blocks/card-failures.spec.js +++ b/tests/e2e/tests/_legacy-experience/woocommerce-blocks/card-failures.spec.js @@ -1,6 +1,5 @@ import { test, expect } from '@playwright/test'; -import config from 'config'; -import { payments } from '../../../utils'; +import { payments, config } from '../../../utils'; const { emptyCart, @@ -37,30 +36,33 @@ const testCard = async ( page, cardKey ) => { .soft( expected ) .toMatch( new RegExp( `(?:${ card.error.join( '|' ) })`, 'i' ) ); }; - +test.slow(); test.describe.configure( { mode: 'parallel' } ); -test.describe( 'customer cannot checkout with invalid cards @blocks', () => { - test( `a declined card shows the correct error message @smoke`, async ( { - page, - } ) => testCard( page, 'cards.declined' ) ); +test.describe( + 'customer cannot checkout with invalid cards @blocks @legacy', + () => { + test( `a declined card shows the correct error message @smoke`, async ( { + page, + } ) => testCard( page, 'cards.declined' ) ); - test( `a card with insufficient funds shows the correct error message`, async ( { - page, - } ) => testCard( page, 'cards.declined-funds' ) ); + test( `a card with insufficient funds shows the correct error message`, async ( { + page, + } ) => testCard( page, 'cards.declined-funds' ) ); - test( `a card with invalid number shows the correct error message`, async ( { - page, - } ) => testCard( page, 'cards.declined-incorrect' ) ); + test( `a card with invalid number shows the correct error message`, async ( { + page, + } ) => testCard( page, 'cards.declined-incorrect' ) ); - test( `an expired card shows the correct error message`, async ( { - page, - } ) => testCard( page, 'cards.declined-expired' ) ); + test( `an expired card shows the correct error message`, async ( { + page, + } ) => testCard( page, 'cards.declined-expired' ) ); - test( `a card with incorrect CVC shows the correct error message @smoke`, async ( { - page, - } ) => testCard( page, 'cards.declined-cvc' ) ); + test( `a card with incorrect CVC shows the correct error message @smoke`, async ( { + page, + } ) => testCard( page, 'cards.declined-cvc' ) ); - test( `an error processing the card shows the correct error message`, async ( { - page, - } ) => testCard( page, 'cards.declined-processing' ) ); -} ); + test( `an error processing the card shows the correct error message`, async ( { + page, + } ) => testCard( page, 'cards.declined-processing' ) ); + } +); diff --git a/tests/e2e/tests/_legacy-experience/woocommerce-blocks/normal-card.spec.js b/tests/e2e/tests/_legacy-experience/woocommerce-blocks/normal-card.spec.js index bf92c31fe..9c4ebb050 100644 --- a/tests/e2e/tests/_legacy-experience/woocommerce-blocks/normal-card.spec.js +++ b/tests/e2e/tests/_legacy-experience/woocommerce-blocks/normal-card.spec.js @@ -1,6 +1,5 @@ import { test, expect } from '@playwright/test'; -import config from 'config'; -import { payments } from '../../../utils'; +import { payments, config } from '../../../utils'; const { emptyCart, @@ -8,8 +7,8 @@ const { fillCreditCardDetailsLegacy, setupBlocksCheckout, } = payments; - -test( 'customer can checkout with a normal credit card @smoke @blocks', async ( { +test.slow(); +test( 'customer can checkout with a normal credit card @smoke @blocks @legacy', async ( { page, } ) => { await emptyCart( page ); @@ -21,7 +20,9 @@ test( 'customer can checkout with a normal credit card @smoke @blocks', async ( await fillCreditCardDetailsLegacy( page, config.get( 'cards.basic' ) ); await page.locator( 'text=Place order' ).click(); - await page.waitForNavigation(); + await page.waitForURL( '**/checkout/order-received/**', { + timeout: 20000, + } ); // Allow some extra time for the redirect to complete. await expect( page.locator( 'h1.entry-title' ) ).toHaveText( 'Order received' diff --git a/tests/e2e/tests/_legacy-experience/woocommerce-blocks/saved-card.spec.js b/tests/e2e/tests/_legacy-experience/woocommerce-blocks/saved-card.spec.js index 0e2b8fd41..ed523c99c 100644 --- a/tests/e2e/tests/_legacy-experience/woocommerce-blocks/saved-card.spec.js +++ b/tests/e2e/tests/_legacy-experience/woocommerce-blocks/saved-card.spec.js @@ -1,6 +1,6 @@ import { test, expect } from '@playwright/test'; -import config from 'config'; -import { payments, api, user } from '../../../utils'; +import { payments, api, config } from '../../../utils'; +import qit from '/qitHelpers'; const { emptyCart, @@ -26,12 +26,12 @@ test.beforeAll( async () => { await api.create.customer( user ); } ); - -test( 'customer can checkout with a saved card @smoke @blocks', async ( { +test.slow(); +test( 'customer can checkout with a saved card @smoke @blocks @legacy', async ( { page, } ) => { await test.step( 'customer login', async () => { - await user.login( + await qit.loginAs( page, username, config.get( 'users.customer.password' ) @@ -51,7 +51,9 @@ test( 'customer can checkout with a saved card @smoke @blocks', async ( { await page.locator( 'text=Place order' ).click(); - await page.waitForNavigation(); + await page.waitForURL( '**/checkout/order-received/**', { + timeout: 20000, + } ); // Allow some extra time for the redirect to complete. await expect( page.locator( 'h1.entry-title' ) ).toHaveText( 'Order received' ); @@ -77,7 +79,9 @@ test( 'customer can checkout with a saved card @smoke @blocks', async ( { await page.locator( 'text=Place order' ).click(); - await page.waitForNavigation(); + await page.waitForURL( '**/checkout/order-received/**', { + timeout: 20000, + } ); // Allow some extra time for the redirect to complete. await expect( page.locator( 'h1.entry-title' ) ).toHaveText( 'Order received' ); diff --git a/tests/e2e/tests/_legacy-experience/woocommerce-blocks/sca-card.spec.js b/tests/e2e/tests/_legacy-experience/woocommerce-blocks/sca-card.spec.js index 93628919f..5fb612a6a 100644 --- a/tests/e2e/tests/_legacy-experience/woocommerce-blocks/sca-card.spec.js +++ b/tests/e2e/tests/_legacy-experience/woocommerce-blocks/sca-card.spec.js @@ -1,6 +1,5 @@ import { test, expect } from '@playwright/test'; -import config from 'config'; -import { payments } from '../../../utils'; +import { payments, config } from '../../../utils'; const { emptyCart, @@ -9,7 +8,8 @@ const { fillCreditCardDetailsLegacy, } = payments; -test( 'customer can checkout with a SCA card @smoke @blocks', async ( { +test.slow(); +test( 'customer can checkout with a SCA card @smoke @blocks @legacy', async ( { page, } ) => { await emptyCart( page ); @@ -30,6 +30,8 @@ test( 'customer can checkout with a SCA card @smoke @blocks', async ( { await page.waitForTimeout( 1000 ); } + await page.waitForTimeout( 3000 ); + await page .frame( { name: 'stripe-challenge-frame', diff --git a/tests/e2e/tests/_legacy-experience/woocommerce-blocks/subscription-product.spec.js b/tests/e2e/tests/_legacy-experience/woocommerce-blocks/subscription-product.spec.js index 799a42985..4bee1d941 100644 --- a/tests/e2e/tests/_legacy-experience/woocommerce-blocks/subscription-product.spec.js +++ b/tests/e2e/tests/_legacy-experience/woocommerce-blocks/subscription-product.spec.js @@ -1,11 +1,10 @@ import { test, expect } from '@playwright/test'; -import config from 'config'; -import { payments, api } from '../../../utils'; +import { payments, api, config } from '../../../utils'; const { setupBlocksCheckout, fillCreditCardDetailsLegacy } = payments; let productId; - +test.slow(); test.beforeAll( async () => { const product = { ...config.get( 'products.subscription' ), @@ -29,7 +28,7 @@ test.afterAll( async () => { await api.deletePost.product( productId ); } ); -test( 'customer can purchase a subscription product @smoke @blocks @subscriptions', async ( { +test( 'customer can purchase a subscription product @smoke @blocks @subscriptions @legacy', async ( { page, } ) => { await page.goto( `?p=${ productId }` ); @@ -46,7 +45,9 @@ test( 'customer can purchase a subscription product @smoke @blocks @subscription await fillCreditCardDetailsLegacy( page, config.get( 'cards.basic' ) ); await page.locator( 'text=Sign up now' ).click(); - await page.waitForNavigation(); + await page.waitForURL( '**/checkout/order-received/**', { + timeout: 20000, + } ); // Allow some extra time for the redirect to complete. await expect( page.locator( 'h1.entry-title' ) ).toHaveText( 'Order received' diff --git a/tests/e2e/tests/checkout/blocks/card-failures.spec.js b/tests/e2e/tests/checkout/blocks/card-failures.spec.js index af766d158..1e0b55637 100644 --- a/tests/e2e/tests/checkout/blocks/card-failures.spec.js +++ b/tests/e2e/tests/checkout/blocks/card-failures.spec.js @@ -1,6 +1,5 @@ import { test, expect } from '@playwright/test'; -import config from 'config'; -import { payments } from '../../../utils'; +import { payments, config } from '../../../utils'; const { emptyCart, @@ -45,7 +44,7 @@ const testCard = async ( page, cardKey ) => { .soft( expected ) .toMatch( new RegExp( `(?:${ card.error.join( '|' ) })`, 'i' ) ); }; - +test.slow(); test.describe.configure( { mode: 'parallel' } ); test.describe( 'customer cannot checkout with invalid cards @blocks', () => { test( `a declined card shows the correct error message @smoke`, async ( { diff --git a/tests/e2e/tests/checkout/blocks/normal-card.spec.js b/tests/e2e/tests/checkout/blocks/normal-card.spec.js index e461fb5ee..38be7ae24 100644 --- a/tests/e2e/tests/checkout/blocks/normal-card.spec.js +++ b/tests/e2e/tests/checkout/blocks/normal-card.spec.js @@ -1,6 +1,5 @@ import { test, expect } from '@playwright/test'; -import config from 'config'; -import { payments } from '../../../utils'; +import { payments, config } from '../../../utils'; const { emptyCart, @@ -9,6 +8,7 @@ const { setupBlocksCheckout, } = payments; +test.slow(); // Make sure that test have enough time to complete. test( 'customer can checkout with a normal credit card @smoke @blocks', async ( { page, } ) => { @@ -21,7 +21,9 @@ test( 'customer can checkout with a normal credit card @smoke @blocks', async ( await fillCreditCardDetails( page, config.get( 'cards.basic' ) ); await page.locator( 'text=Place order' ).click(); - await page.waitForNavigation(); + await page.waitForURL( '**/checkout/order-received/**', { + timeout: 20000, + } ); // Allow some extra time for the redirect to complete. await expect( page.locator( 'h1.entry-title' ) ).toHaveText( 'Order received' diff --git a/tests/e2e/tests/checkout/blocks/saved-card.spec.js b/tests/e2e/tests/checkout/blocks/saved-card.spec.js index db557e134..ee4b8e3f8 100644 --- a/tests/e2e/tests/checkout/blocks/saved-card.spec.js +++ b/tests/e2e/tests/checkout/blocks/saved-card.spec.js @@ -1,6 +1,7 @@ import { test, expect } from '@playwright/test'; -import config from 'config'; -import { payments, api, user } from '../../../utils'; +import { payments, api, config } from '../../../utils'; + +import qit from '/qitHelpers'; const { emptyCart, @@ -10,6 +11,7 @@ const { } = payments; let username, userEmail; +test.slow(); // Make sure test has enough time to complete. test.beforeAll( async () => { // This allow multiple tests to run in parallel. @@ -31,7 +33,7 @@ test( 'customer can checkout with a saved card @smoke @blocks', async ( { page, } ) => { await test.step( 'customer login', async () => { - await user.login( + await qit.loginAs( page, username, config.get( 'users.customer.password' ) @@ -51,7 +53,9 @@ test( 'customer can checkout with a saved card @smoke @blocks', async ( { await page.locator( 'text=Place order' ).click(); - await page.waitForNavigation(); + await page.waitForURL( '**/checkout/order-received/**', { + timeout: 20000, + } ); // Allow some extra time for the redirect to complete. await expect( page.locator( 'h1.entry-title' ) ).toHaveText( 'Order received' ); @@ -77,7 +81,9 @@ test( 'customer can checkout with a saved card @smoke @blocks', async ( { await page.locator( 'text=Place order' ).click(); - await page.waitForNavigation(); + await page.waitForURL( '**/checkout/order-received/**', { + timeout: 20000, + } ); // Allow some extra time for the redirect to complete. await expect( page.locator( 'h1.entry-title' ) ).toHaveText( 'Order received' ); diff --git a/tests/e2e/tests/checkout/blocks/sca-card.spec.js b/tests/e2e/tests/checkout/blocks/sca-card.spec.js index 1cb1ee434..91ed831c3 100644 --- a/tests/e2e/tests/checkout/blocks/sca-card.spec.js +++ b/tests/e2e/tests/checkout/blocks/sca-card.spec.js @@ -1,6 +1,5 @@ import { test, expect } from '@playwright/test'; -import config from 'config'; -import { payments } from '../../../utils'; +import { payments, config } from '../../../utils'; const { emptyCart, @@ -9,6 +8,7 @@ const { fillCreditCardDetails, } = payments; +test.slow(); // Make sure that test have enough time to complete. test( 'customer can checkout with a SCA card @smoke @blocks', async ( { page, } ) => { diff --git a/tests/e2e/tests/checkout/blocks/subscription-product.spec.js b/tests/e2e/tests/checkout/blocks/subscription-product.spec.js index facf28db9..0acd55dcb 100644 --- a/tests/e2e/tests/checkout/blocks/subscription-product.spec.js +++ b/tests/e2e/tests/checkout/blocks/subscription-product.spec.js @@ -1,11 +1,11 @@ import { test, expect } from '@playwright/test'; -import config from 'config'; -import { payments, api } from '../../../utils'; +import { payments, api, config } from '../../../utils'; const { setupBlocksCheckout, fillCreditCardDetails } = payments; let productId; +test.slow(); test.beforeAll( async () => { const product = { ...config.get( 'products.subscription' ), @@ -46,7 +46,9 @@ test( 'customer can purchase a subscription product @smoke @blocks @subscription await fillCreditCardDetails( page, config.get( 'cards.basic' ) ); await page.locator( 'text=Sign up now' ).click(); - await page.waitForNavigation(); + await page.waitForURL( '**/checkout/order-received/**', { + timeout: 20000, + } ); // Allow some extra time for the redirect to complete. await expect( page.locator( 'h1.entry-title' ) ).toHaveText( 'Order received' diff --git a/tests/e2e/tests/checkout/shortcode/card-failures.spec.js b/tests/e2e/tests/checkout/shortcode/card-failures.spec.js index 20ce9f214..0d30eaa2a 100644 --- a/tests/e2e/tests/checkout/shortcode/card-failures.spec.js +++ b/tests/e2e/tests/checkout/shortcode/card-failures.spec.js @@ -1,6 +1,5 @@ import { test, expect } from '@playwright/test'; -import config from 'config'; -import { payments } from '../../../utils'; +import { payments, config } from '../../../utils'; const { emptyCart, @@ -22,13 +21,14 @@ const testCard = async ( page, cardKey ) => { const card = config.get( cardKey ); await fillCreditCardDetailsShortcode( page, card ); - await page.locator( 'text=Place order' ).click(); + await page.locator( '#place_order' ).first().click(); expect .soft( await page.innerText( '.woocommerce-error' ) ) .toMatch( new RegExp( `(?:${ card.error.join( '|' ) })`, 'i' ) ); }; +test.slow(); test.describe.configure( { mode: 'parallel' } ); test.describe( 'customer cannot checkout with invalid cards', () => { test( `a declined card shows the correct error message @smoke`, async ( { diff --git a/tests/e2e/tests/checkout/shortcode/normal-card.spec.js b/tests/e2e/tests/checkout/shortcode/normal-card.spec.js index 8df8d3cc1..5d404b74a 100644 --- a/tests/e2e/tests/checkout/shortcode/normal-card.spec.js +++ b/tests/e2e/tests/checkout/shortcode/normal-card.spec.js @@ -1,6 +1,5 @@ import { test, expect } from '@playwright/test'; -import config from 'config'; -import { payments } from '../../../utils'; +import { payments, config } from '../../../utils'; const { emptyCart, @@ -9,6 +8,7 @@ const { fillCreditCardDetailsShortcode, } = payments; +test.slow(); test( 'customer can checkout with a normal credit card @smoke', async ( { page, } ) => { @@ -19,8 +19,10 @@ test( 'customer can checkout with a normal credit card @smoke', async ( { config.get( 'addresses.customer.billing' ) ); await fillCreditCardDetailsShortcode( page, config.get( 'cards.basic' ) ); - await page.locator( 'text=Place order' ).click(); - await page.waitForNavigation(); + await page.locator( '#place_order' ).first().click(); + await page.waitForURL( '**/checkout/order-received/**', { + timeout: 20000, + } ); // Allow some extra time for the redirect to complete. await expect( page.locator( 'h1.entry-title' ) ).toHaveText( 'Order received' diff --git a/tests/e2e/tests/checkout/shortcode/saved-card.spec.js b/tests/e2e/tests/checkout/shortcode/saved-card.spec.js index 5c1d73f82..104f248cd 100644 --- a/tests/e2e/tests/checkout/shortcode/saved-card.spec.js +++ b/tests/e2e/tests/checkout/shortcode/saved-card.spec.js @@ -1,6 +1,6 @@ import { test, expect } from '@playwright/test'; -import config from 'config'; -import { payments, api, user } from '../../../utils'; +import { payments, api, config } from '../../../utils'; +import qit from '/qitHelpers'; const { emptyCart, @@ -10,6 +10,7 @@ const { } = payments; let username, userEmail; +test.slow(); // Make sure test has enough time to complete. test.beforeAll( async () => { // This allow multiple tests to run in parallel. @@ -29,7 +30,7 @@ test.beforeAll( async () => { test( 'customer can checkout with a saved card @smoke', async ( { page } ) => { await test.step( 'customer login', async () => { - await user.login( + await qit.loginAs( page, username, config.get( 'users.customer.password' ) @@ -48,9 +49,11 @@ test( 'customer can checkout with a saved card @smoke', async ( { page } ) => { // check box to save payment method. await page.locator( '#wc-stripe-new-payment-method' ).click(); - await page.locator( 'text=Place order' ).click(); + await page.locator( '#place_order' ).first().click(); - await page.waitForNavigation(); + await page.waitForURL( '**/checkout/order-received/**', { + timeout: 20000, + } ); // Allow some extra time for the redirect to complete. await expect( page.locator( 'h1.entry-title' ) ).toHaveText( 'Order received' ); @@ -68,9 +71,11 @@ test( 'customer can checkout with a saved card @smoke', async ( { page } ) => { ) ).toHaveCount( 1 ); - await page.locator( 'text=Place order' ).click(); + await page.locator( '#place_order' ).first().click(); - await page.waitForNavigation(); + await page.waitForURL( '**/checkout/order-received/**', { + timeout: 20000, + } ); // Allow some extra time for the redirect to complete. await expect( page.locator( 'h1.entry-title' ) ).toHaveText( 'Order received' ); diff --git a/tests/e2e/tests/checkout/shortcode/sca-card.spec.js b/tests/e2e/tests/checkout/shortcode/sca-card.spec.js index 74aef73e4..987c9e99c 100644 --- a/tests/e2e/tests/checkout/shortcode/sca-card.spec.js +++ b/tests/e2e/tests/checkout/shortcode/sca-card.spec.js @@ -1,6 +1,5 @@ import { test, expect } from '@playwright/test'; -import config from 'config'; -import { payments } from '../../../utils'; +import { payments, config } from '../../../utils'; const { emptyCart, @@ -9,6 +8,7 @@ const { fillCreditCardDetailsShortcode, } = payments; +test.slow(); test( 'customer can checkout with a SCA card @smoke', async ( { page } ) => { await emptyCart( page ); await setupCart( page ); @@ -17,7 +17,7 @@ test( 'customer can checkout with a SCA card @smoke', async ( { page } ) => { config.get( 'addresses.customer.billing' ) ); await fillCreditCardDetailsShortcode( page, config.get( 'cards.3ds' ) ); - await page.locator( 'text=Place order' ).click(); + await page.locator( '#place_order' ).first().click(); // Wait until the SCA frame is available while ( diff --git a/tests/e2e/tests/checkout/shortcode/subscription-product.spec.js b/tests/e2e/tests/checkout/shortcode/subscription-product.spec.js index e7e043efe..0fab28abc 100644 --- a/tests/e2e/tests/checkout/shortcode/subscription-product.spec.js +++ b/tests/e2e/tests/checkout/shortcode/subscription-product.spec.js @@ -1,6 +1,5 @@ import { test, expect } from '@playwright/test'; -import config from 'config'; -import { payments, api } from '../../../utils'; +import { payments, api, config } from '../../../utils'; const { setupShortcodeCheckout, fillCreditCardDetailsShortcode } = payments; @@ -29,6 +28,7 @@ test.afterAll( async () => { await api.deletePost.product( productId ); } ); +test.slow(); test( 'customer can purchase a subscription product @smoke @subscriptions', async ( { page, } ) => { @@ -45,8 +45,10 @@ test( 'customer can purchase a subscription product @smoke @subscriptions', asyn await setupShortcodeCheckout( page, customerData ); await fillCreditCardDetailsShortcode( page, config.get( 'cards.basic' ) ); - await page.locator( 'text=Sign up now' ).click(); - await page.waitForNavigation(); + await page.locator( 'text=Sign up now' ).first().click(); + await page.waitForURL( '**/checkout/order-received/**', { + timeout: 20000, + } ); // Allow some extra time for the redirect to complete. await expect( page.locator( 'h1.entry-title' ) ).toHaveText( 'Order received' diff --git a/tests/e2e/tests/orders/full-refund.spec.js b/tests/e2e/tests/orders/full-refund.spec.js index bfab3907b..f4f76a66e 100644 --- a/tests/e2e/tests/orders/full-refund.spec.js +++ b/tests/e2e/tests/orders/full-refund.spec.js @@ -1,7 +1,8 @@ import stripe from 'stripe'; import { test, expect } from '@playwright/test'; -import config from 'config'; -import { api, payments } from '../../utils'; + +import qit from '/qitHelpers'; +import { api, payments, config } from '../../utils'; const { emptyCart, @@ -10,11 +11,12 @@ const { fillCreditCardDetailsShortcode, } = payments; +test.slow(); test( 'merchant can issue a full refund @smoke', async ( { browser } ) => { let orderId, stripeChargeId, stripeRefundId; const adminContext = await browser.newContext( { - storageState: process.env.ADMINSTATE, + storageState: qit.getEnv( 'ADMINSTATE' ), } ); const adminPage = await adminContext.newPage(); @@ -33,7 +35,7 @@ test( 'merchant can issue a full refund @smoke', async ( { browser } ) => { userPage, config.get( 'cards.basic' ) ); - await userPage.locator( 'text=Place order' ).click(); + await userPage.locator( '#place_order' ).first().click(); await userPage.waitForURL( '**/checkout/order-received/**' ); await expect( userPage.locator( 'h1.entry-title' ) ).toHaveText( @@ -101,7 +103,7 @@ test( 'merchant can issue a full refund @smoke', async ( { browser } ) => { ); await test.step( 'check Stripe payment status ', async () => { - const stripeClient = stripe( process.env.STRIPE_SECRET_KEY ); + const stripeClient = stripe( qit.getEnv( 'STRIPE_SECRET_KEY' ) ); const charge = await stripeClient.charges.retrieve( stripeChargeId, { expand: [ 'refunds' ], diff --git a/tests/e2e/tests/subscriptions/subscription-renewal.spec.js b/tests/e2e/tests/subscriptions/subscription-renewal.spec.js index 369925250..04c36057e 100644 --- a/tests/e2e/tests/subscriptions/subscription-renewal.spec.js +++ b/tests/e2e/tests/subscriptions/subscription-renewal.spec.js @@ -1,11 +1,12 @@ import { test, expect } from '@playwright/test'; -import config from 'config'; -import { payments, api, user } from '../../utils'; +import { payments, api, config } from '../../utils'; +import qit from '/qitHelpers'; const { setupShortcodeCheckout, fillCreditCardDetailsShortcode } = payments; let productId; let username, userEmail; +test.slow(); // Make sure test has enough time to complete. test.beforeAll( async () => { // This allow multiple tests to run in parallel. @@ -48,7 +49,7 @@ test( 'customer can renew a subscription @smoke @subscriptions', async ( { page, } ) => { await test.step( 'customer login', async () => { - await user.login( + await qit.loginAs( page, username, config.get( 'users.customer.password' ) @@ -65,8 +66,10 @@ test( 'customer can renew a subscription @smoke @subscriptions', async ( { config.get( 'cards.basic' ) ); - await page.locator( 'text=Sign up now' ).click(); - + await page.locator( 'text=Sign up now' ).first().click(); + await page.waitForURL( '**/checkout/order-received/**', { + timeout: 30000, + } ); // Allow some extra time for the redirect to complete. await expect( page.locator( 'h1.entry-title' ) ).toHaveText( 'Order received' ); @@ -87,6 +90,9 @@ test( 'customer can renew a subscription @smoke @subscriptions', async ( { 'input[id^="radio-control-wc-payment-method-saved-tokens-"]' ); await page.click( 'text=Renew subscription' ); + await page.waitForURL( '**/checkout/order-received/**', { + timeout: 30000, + } ); // Allow some extra time for the redirect to complete. await expect( page.locator( 'h1.entry-title' ) ).toHaveText( 'Order received' ); diff --git a/tests/e2e/utils/api.js b/tests/e2e/utils/api.js index c46859586..32188fc17 100644 --- a/tests/e2e/utils/api.js +++ b/tests/e2e/utils/api.js @@ -1,14 +1,15 @@ import wcApi from '@woocommerce/woocommerce-rest-api'; -import config from '../config/playwright.config'; +import qit from '/qitHelpers'; +const config = require( '/qit/tests/e2e/qit-playwright.config' ); let api; // Ensure that global-setup.js runs before creating api client -if ( process.env.CONSUMER_KEY && process.env.CONSUMER_SECRET ) { +if ( qit.getEnv( 'CONSUMER_KEY' ) && qit.getEnv( 'CONSUMER_SECRET' ) ) { api = new wcApi( { url: config.use.baseURL, - consumerKey: process.env.CONSUMER_KEY, - consumerSecret: process.env.CONSUMER_SECRET, + consumerKey: qit.getEnv( 'CONSUMER_KEY' ), + consumerSecret: qit.getEnv( 'CONSUMER_SECRET' ), version: 'wc/v3', } ); } diff --git a/tests/e2e/utils/config.js b/tests/e2e/utils/config.js new file mode 100644 index 000000000..c7ba9beba --- /dev/null +++ b/tests/e2e/utils/config.js @@ -0,0 +1,5 @@ +// Define the config directory for the tests. +const path = require( 'path' ); +process.env[ 'NODE_CONFIG_DIR' ] = path.resolve( __dirname, '../config/' ); + +export const config = require( 'config' ); diff --git a/tests/e2e/utils/index.js b/tests/e2e/utils/index.js index 9b23cb518..e1a30e4cf 100644 --- a/tests/e2e/utils/index.js +++ b/tests/e2e/utils/index.js @@ -1,9 +1,9 @@ import * as api from './api'; import * as payments from './payments'; -import * as user from './user'; +import { config } from './config'; module.exports = { api, payments, - user, + config, }; diff --git a/tests/e2e/utils/payments.js b/tests/e2e/utils/payments.js index b6efdb4a9..f43ef7bb9 100644 --- a/tests/e2e/utils/payments.js +++ b/tests/e2e/utils/payments.js @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import config from 'config'; +import { config } from './config'; /** * Empty the WC cart. diff --git a/tests/e2e/utils/playwright-setup.js b/tests/e2e/utils/playwright-setup.js deleted file mode 100644 index 56f0c8000..000000000 --- a/tests/e2e/utils/playwright-setup.js +++ /dev/null @@ -1,465 +0,0 @@ -import * as dotenv from 'dotenv'; -import path from 'path'; -import fs from 'fs'; - -import stripe from 'stripe'; - -import { expect } from '@playwright/test'; -import { NodeSSH } from 'node-ssh'; -import { downloadRelease } from './plugin-utils'; - -dotenv.config( { - path: `${ process.env.E2E_ROOT }/config/local.env`, -} ); - -import { user } from '.'; - -const { - ADMIN_USER, - E2E_ROOT, - PLUGIN_REPOSITORY, - PLUGIN_VERSION, - STRIPE_PUB_KEY, - STRIPE_SECRET_KEY, - SSH_HOST, - SSH_USER, - SSH_PASSWORD, - SSH_PATH, -} = process.env; - -/** - * Helper function to login a WP user and save the state on a given path. - * @param {Page} page Playwright page object. - * @param {string} username Username of the user to login. - * @param {string} password Password of the user to login. - * @param {string} statePath Path to save the state. - * @param {number} retries Number of retries to login. - * @return {Promise} Promise object represents the state of the operation. - */ -export const loginCustomerAndSaveState = ( { - page, - username, - password, - statePath, - retries, -} ) => - new Promise( ( resolve ) => { - ( async () => { - console.log( '- Trying to log-in as customer...' ); - await user.login( page, username, password, retries ); - - await page.goto( `/my-account` ); - await expect( - page.locator( - '.woocommerce-MyAccount-navigation-link--customer-logout' - ) - ).toBeVisible(); - await expect( - page.locator( 'div.woocommerce-MyAccount-content > p >> nth=0' ) - ).toContainText( 'Hello' ); - - await page.context().storageState( { path: statePath } ); - console.log( '\u2714 Logged-in as customer successfully.' ); - resolve(); - } )(); - } ); - -/** - * Helper function to login a WP admin user and save the state on a given path. - * @param {Page} page Playwright page object. - * @param {string} username Username of the user to login. - * @param {string} password Password of the user to login. - * @param {string} statePath Path to save the state. - * @param {number} retries Number of retries to login. - * @return {Promise} Promise object represents the state of the operation. - */ -export const loginAdminAndSaveState = ( { - page, - username, - password, - statePath, - retries, -} ) => - new Promise( ( resolve ) => { - ( async () => { - // Sign in as admin user and save state - console.log( '- Trying to log-in as admin...' ); - await user.login( page, username, password, retries ); - - await page.goto( `/wp-admin` ); - - await expect( page.locator( 'div.wrap > h1' ) ).toHaveText( - 'Dashboard' - ); - await page.context().storageState( { path: statePath } ); - console.log( '\u2714 Logged-in as admin successfully.' ); - resolve(); - } )(); - } ); - -/** - * Helper function to create WC API tokens and save them as env variables. - * This function is used when the admin user is already logged in. - * @param {Page} page Playwright page object. - * @return {Promise} Promise object represents the state of the operation. - */ -export const createApiTokens = ( page ) => - new Promise( ( resolve, reject ) => { - ( async () => { - const nRetries = 5; - for ( let i = 0; i < nRetries; i++ ) { - try { - console.log( '- Trying to add consumer token...' ); - await page.goto( - `/wp-admin/admin.php?page=wc-settings&tab=advanced§ion=keys&create-key=1` - ); - await page.fill( '#key_description', 'Key for API access' ); - await page.selectOption( '#key_permissions', 'read_write' ); - await page.click( 'text=Generate API key' ); - process.env.CONSUMER_KEY = await page.inputValue( - '#key_consumer_key' - ); - process.env.CONSUMER_SECRET = await page.inputValue( - '#key_consumer_secret' - ); - console.log( '\u2714 Added consumer token successfully.' ); - resolve(); - return; - } catch ( e ) { - console.log( - `Failed to add consumer token. Retrying... ${ i }/${ nRetries }` - ); - console.log( e ); - } - } - reject(); - } )(); - } ); - -/** - * Helper function to download the Stripe plugin from the repository and install it on the site. - * This is useful when we want to test a specific version of the plugin. - * If the plugin is already installed, it will be updated to the specified version. - * @param {Page} page Playwright page object. - * @returns {Promise} Promise that resolves when the plugin is installed. - */ -export const installPluginFromRepository = ( page ) => - new Promise( ( resolve ) => { - ( async () => { - console.log( - `- Trying to install plugin version ${ PLUGIN_VERSION } from repository ${ PLUGIN_REPOSITORY }...` - ); - const pluginSlug = 'woocommerce-gateway-stripe'; - const pluginZipPath = path.resolve( - __dirname, - `../../tmp/${ pluginSlug }.zip` - ); - - // Download the needed plugin. - await downloadRelease( { - repo: PLUGIN_REPOSITORY, - releaseTag: PLUGIN_VERSION, - downloadPath: pluginZipPath, - filename: `${ pluginSlug }.zip`, - } ); - await page.goto( 'wp-admin/plugin-install.php?tab=upload', { - waitUntil: 'networkidle', - } ); - - await page.setInputFiles( 'input#pluginzip', pluginZipPath, { - timeout: 10000, - } ); - await page.click( "input[type='submit'] >> text=Install Now" ); - - try { - await page.click( 'text=Replace current with uploaded', { - timeout: 10000, - } ); - - await expect( - page.locator( '#wpbody-content .wrap' ) - ).toContainText( - /Plugin (?:downgraded|updated) successfully/gi - ); - } catch ( e ) { - // Stripe wasn't installed on this site. - await expect( - page.locator( '#wpbody-content .wrap' ) - ).toContainText( /Plugin installed successfully/gi ); - - await page.click( 'text=Activate Plugin', { - timeout: 10000, - } ); - } - - await page.goto( 'wp-admin/plugins.php', { - waitUntil: 'networkidle', - } ); - - // Assert that the plugin is listed and active - await expect( - page.locator( `#deactivate-${ pluginSlug }` ) - ).toBeVisible(); - - console.log( - `\u2714 Plugin version ${ PLUGIN_VERSION } installed successfully.` - ); - - resolve(); - } )(); - } ); - -/** - * Helper function to download and install the latest WooCommerce Subscriptions release from the official repository. - * @param {Page} page Playwright page object. - * @returns {Promise} A promise that resolves when the plugin is installed. - */ -export const installWooSubscriptionsFromRepo = ( page ) => - new Promise( ( resolve ) => { - ( async () => { - console.log( - `- Trying to install latest Woo Subscriptions release from official repository...` - ); - - const pluginSlug = 'woocommerce-subscriptions'; - const pluginZipPath = path.resolve( - __dirname, - `../../tmp/${ pluginSlug }.zip` - ); - - // Download the needed plugin. - await downloadRelease( { - repo: 'woocommerce/woocommerce-subscriptions', - releaseTag: 'latest', - filename: 'woocommerce-subscriptions.zip', - downloadPath: pluginZipPath, - } ); - await page.goto( 'wp-admin/plugin-install.php?tab=upload', { - waitUntil: 'networkidle', - } ); - - await page.setInputFiles( 'input#pluginzip', pluginZipPath, { - timeout: 10000, - } ); - await page.click( "input[type='submit'] >> text=Install Now" ); - - try { - await page.click( 'text=Replace current with uploaded', { - timeout: 10000, - } ); - - await expect( - page.locator( '#wpbody-content .wrap' ) - ).toContainText( - /Plugin (?:downgraded|updated) successfully/gi - ); - } catch ( e ) { - // Plugin wasn't installed on this site. - await expect( - page.locator( '#wpbody-content .wrap' ) - ).toContainText( /Plugin installed successfully/gi ); - - await page.click( 'text=Activate Plugin', { - timeout: 10000, - } ); - } - - await page.goto( 'wp-admin/plugins.php', { - waitUntil: 'networkidle', - } ); - - // Assert that the plugin is listed and active - await expect( - page.locator( - `#deactivate-${ pluginSlug }, #deactivate-woocommerce-com-${ pluginSlug }` - ) - ).toBeVisible(); - - console.log( - `\u2714 Woo Subscriptions plugin installed successfully.` - ); - - resolve(); - } )(); - } ); - -/** - * Helper function to run an array of commands in a SSH server. - * @param {Array.} commands The array of commands. - * @returns The promise for the SSH connection. - */ -const sshExecCommands = async ( commands ) => { - const ssh = new NodeSSH(); - const credentials = getServerCredentialsFromEnv(); - return ssh.connect( credentials ).then( async () => { - for ( const command of commands ) { - console.log( - `${ command.substring( 0, 100 ) }${ - command.length <= 100 ? '' : '...' - }` - ); - await ssh - .execCommand( command, { cwd: credentials.path } ) - .then( ( result ) => { - console.log( - `${ result.stdout.substring( 0, 100 ) }${ - result.stdout.length <= 100 ? '' : '...' - }` - ); - } ); - } - } ); -}; - -/** - * Helper function to get the SSH credentials from the env variables. - * @returns the credentials inside an object that is ready to be used in NodeSSH. - */ -const getServerCredentialsFromEnv = () => { - return { - host: SSH_HOST.replace( /\/$/, '' ), - username: SSH_USER, - password: SSH_PASSWORD, - path: SSH_PATH, - }; -}; - -/** - * Helper function to perform the WooCommerce setup over SSH. - * @returns The promise for the SSH connection. - */ -export const setupWoo = async () => { - const shortcodeCartPostContent = fs - .readFileSync( - path.resolve( E2E_ROOT, './test-data/cart-shortcode-content.html' ), - 'utf8' - ) - .replace( '\n', '' ); - const shortcodeCheckoutPostContent = fs - .readFileSync( - path.resolve( - E2E_ROOT, - './test-data/checkout-shortcode-content.html' - ), - 'utf8' - ) - .replace( '\n', '' ); - - const setupCommands = [ - 'wp config set WP_DEBUG false --raw', - 'wp plugin install woocommerce --force --activate', - 'wp plugin install woocommerce-gateway-stripe --activate', - 'wp plugin install disable-emails --activate', // Disable emails to avoid spamming the store owner. - 'wp theme install storefront --activate', - '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_coming_soon "no"', - `wp wc --user=${ ADMIN_USER } tool run install_pages`, - 'wp plugin install wordpress-importer --activate', - 'wp import wp-content/plugins/woocommerce/sample-data/sample_products.xml --authors=skip', - `wp wc shipping_zone create --name="Everywhere" --order=1 --user=${ ADMIN_USER }`, - `wp wc shipping_zone_method create 1 --method_id="flat_rate" --user=${ ADMIN_USER }`, - `wp wc shipping_zone_method create 1 --method_id="free_shipping" --user=${ ADMIN_USER }`, - `wp option update --format=json woocommerce_flat_rate_1_settings '{"title":"Flat rate","tax_status":"taxable","cost":"10"}'`, - `wp post create --post_type=page --post_title='Cart Shortcode' --post_name='cart-shortcode' --post_status=publish --page_template='template-fullwidth.php' --post_content='${ shortcodeCartPostContent }'`, - `wp post create --post_type=page --post_title='Checkout Shortcode' --post_name='checkout-shortcode' --post_status=publish --page_template='template-fullwidth.php' --post_content='${ shortcodeCheckoutPostContent }'`, - ]; - - return sshExecCommands( setupCommands ); -}; - -/** - * Helper function to perform the Stripe plugin setup. - * @param {Page} page The page object. - * @param {string} baseUrl The base URL for the site. - * @returns The promise that resolves when the Stripe plugin is setup. - */ -export const setupStripe = ( page, baseUrl ) => - new Promise( ( resolve, reject ) => { - ( async () => { - try { - // Clean up previous Stripe settings. - await sshExecCommands( [ - 'wp option delete woocommerce_stripe_settings', - ] ); - - const stripeClient = stripe( process.env.STRIPE_SECRET_KEY ); - - // Clean-up previous webhooks for this URL. We can only get the Webhook secret via API when it's created. - const webhookURL = `${ baseUrl }?wc-api=wc_stripe`; - - await stripeClient.webhookEndpoints - .list() - .then( ( result ) => - result.data.filter( ( w ) => w.url == webhookURL ) - ) - .then( async ( webhooks ) => { - for ( const webhook of webhooks ) { - stripeClient.webhookEndpoints.del( webhook.id ); - } - } ); - - // Create a new webhook. - const webhookEndpoint = await stripeClient.webhookEndpoints.create( - { - url: webhookURL, - enabled_events: [ '*' ], - description: 'Webhook created for E2E tests.', - } - ); - - const settings = { - enabled: 'yes', - title: 'Credit Card (Stripe)', - description: 'Pay with your credit card via Stripe.', - api_credentials: '', - testmode: 'yes', - test_publishable_key: STRIPE_PUB_KEY, - test_secret_key: STRIPE_SECRET_KEY, - publishable_key: '', - secret_key: '', - webhook: '', - test_webhook_secret: webhookEndpoint.secret, - webhook_secret: '', - inline_cc_form: 'no', - statement_descriptor: '', - short_statement_descriptor: '', - capture: 'yes', - payment_request: 'yes', - payment_request_button_type: 'buy', - payment_request_button_theme: 'dark', - payment_request_button_locations: [ - 'product', - 'cart', - 'checkout', - ], - payment_request_button_size: 'default', - saved_cards: 'yes', - logging: 'no', - upe_checkout_experience_enabled: 'yes', - }; - - await sshExecCommands( [ - `wp option set woocommerce_stripe_settings --format=json '${ JSON.stringify( - settings - ) }'`, - ] ); - - console.log( - '\u2714 Updated Stripe plugin settings successfully.' - ); - resolve(); - return; - } catch ( e ) { - reject( e ); - } - - reject(); - } )(); - } ); diff --git a/tests/e2e/utils/plugin-utils.js b/tests/e2e/utils/plugin-utils.js deleted file mode 100644 index 34f6e34b7..000000000 --- a/tests/e2e/utils/plugin-utils.js +++ /dev/null @@ -1,71 +0,0 @@ -import * as dotenv from 'dotenv'; -import axios from 'axios'; -import fs from 'fs'; -import path from 'path'; - -dotenv.config( { - path: `${ process.env.E2E_ROOT }/config/local.env`, -} ); - -const { GITHUB_TOKEN } = process.env; - -const getReleaseInfo = async ( { repo, releaseTag } ) => { - const options = { - url: `https://api.github.com/repos/${ repo }/releases/${ - releaseTag === 'latest' ? '' : 'tags/' - }${ releaseTag }`, - headers: { - Authorization: `token ${ GITHUB_TOKEN }`, - }, - }; - - // Make a request to the GitHub API to get information about the release - const releaseInfo = await axios( options ).catch( ( { response } ) => { - console.error( - `GitHub API request failed: [${ response.status }] ${ response.data.message }` - ); - process.exit( 1 ); - } ); - - // Return the release information - return releaseInfo.data; -}; - -export const downloadRelease = async ( { - repo, - releaseTag, - filename, - downloadPath, -} ) => { - // Create destination folder. - const dir = path.dirname( downloadPath ); - fs.mkdirSync( dir, { recursive: true } ); - - // Get the release information - const release = await getReleaseInfo( { repo, releaseTag } ); - - // Find the asset that has the specified filename - const asset = release.assets.find( ( asset ) => asset.name === filename ); - - if ( ! asset ) { - throw new Error( - `Asset ${ filename } not found in release ${ releaseTag } of repository ${ repo }` - ); - } - - // Download the asset - const options = { - method: 'GET', - url: asset.url, - responseType: 'stream', - headers: { - Authorization: `token ${ GITHUB_TOKEN }`, - Accept: 'application/octet-stream', - }, - }; - - const response = await axios( options ); - - // Return the response data as a stream - return response.data.pipe( fs.createWriteStream( downloadPath ) ); -}; diff --git a/tests/e2e/utils/user.js b/tests/e2e/utils/user.js deleted file mode 100644 index 440ac0fd6..000000000 --- a/tests/e2e/utils/user.js +++ /dev/null @@ -1,45 +0,0 @@ -import { expect } from '@playwright/test'; - -/** - * Logs in a user with the given credentials on the provided page, with retries if login fails. - * @param {Page} page - The Playwright page object to use for the login process. - * @param {string} username - The username to use for the login process. - * @param {string} password - The password to use for the login process. - * @param {number} [retries=3] - The number of retries for the login process in case of failure. - * @throws {Error} Will throw an error if login fails after the specified number of retries. - * @returns {Promise} - A promise that resolves when the login process is complete. - */ -export async function login( page, username, password, retries = 3 ) { - for ( let i = 1; i <= retries; i++ ) { - try { - await page.goto( `/wp-admin` ); - await page.waitForLoadState( 'networkidle' ); - - if ( await page.url().includes( 'wp-login.php' ) ) { - await page.fill( 'input[name="log"]', username ); - await page.fill( 'input[name="pwd"]', password ); - await page.click( 'input[value="Log In"]' ); - } - await page.waitForLoadState( 'networkidle' ); - - if ( await page.$( 'body.logged-in' ) ) { - // customer login - return; - } else { - // admin login - await expect( page.locator( 'div.wrap > h1' ) ).toHaveText( - 'Dashboard' - ); - return; - } - } catch ( e ) { - console.error( - `User log-in failed, Retrying... ${ i }/${ retries }.`, - e - ); - } - } - throw new Error( - `User log-in failed for user ${ username } after ${ retries } attempts.` - ); -}