Merge branch 'master' of github.com:woocommerce/woocommerce into add/e2e-merchant-product-views
This commit is contained in:
commit
0fea99f6be
|
@ -55,7 +55,7 @@ This section explains how e2e tests are working behind the scenes. These are not
|
|||
|
||||
### Test Environment
|
||||
|
||||
We recommend using Docker for running tests locally in order for the test environment to match the setup on Travis CI (where Docker is also used for running tests). [An official WordPress Docker image](https://github.com/docker-library/docs/blob/master/wordpress/README.md) is used to build the site. Once the site using the WP Docker image is built, the current WooCommerce dev branch is being copied to the `plugins` folder of that newly built test site. No WooCommerce Docker image is being built or needed.
|
||||
We recommend using Docker for running tests locally in order for the test environment to match the setup on Travis CI (where Docker is also used for running tests). [An official WordPress Docker image](https://github.com/docker-library/docs/blob/master/wordpress/README.md) is used to build the site. Once the site using the WP Docker image is built, the current WooCommerce dev branch is mapped into the `plugins` folder of that newly built test site.
|
||||
|
||||
### Test Variables
|
||||
|
||||
|
@ -81,9 +81,9 @@ If you need to modify the port for your local test environment (eg. port is alre
|
|||
|
||||
### Jest test sequencer
|
||||
|
||||
[Jest](https://jestjs.io/) is being used to run e2e tests. By default, jest runs tests ordered by the time it takes to run the test (the test that takes longer to run will be run first, the test that takes less time to run will run last). Jest sequencer introduces tools that can be used to specify the order in which the tests are being run. In our case, they are being run in alphabetical order of the directories where tests are located. This way, tests in the new directory `activate-and-setup` will run first.
|
||||
[Jest](https://jestjs.io/) is being used to run e2e tests. Jest sequencer introduces tools that can be used to specify the order in which the tests are being run. In our case, they are being run in alphabetical order of the directories where tests are located. This way, tests in the new directory `activate-and-setup` will run first. By default, jest runs tests ordered by the time it takes to run the test (the test that takes longer to run will be run first, the test that takes less time to run will run last).
|
||||
|
||||
Setup Wizard e2e test (located in `activate-and-setup` directory) will run before all other tests. This will allow making sure that WooCommerce is activated on the site and for the setup wizard to be completed on a brand new install of WooCommerce.
|
||||
The Setup Wizard e2e test (located in `activate-and-setup` directory) will run first. This ensures that WooCommerce is active and the setup wizard has been completed. This is necessary because `docker:up` creates a brand new install of WordPress and WooCommerce.
|
||||
|
||||
### Chromium Download
|
||||
|
||||
|
@ -111,7 +111,7 @@ Puppeteer will still automatically download Chromium when needed.
|
|||
|
||||
- Run `npm run build:assets`
|
||||
|
||||
- Run `npm install jest --global`
|
||||
- Run `npm install jest --global` (this only needs to be done once)
|
||||
|
||||
- Run `npx wc-e2e docker:up` - it will build the test site using Docker.
|
||||
|
||||
|
@ -139,7 +139,7 @@ Username: admin
|
|||
PW: password
|
||||
```
|
||||
|
||||
- Run `npx wc-e2e docker:down` when you are done with running e2e tests or when making any changes to test suite.
|
||||
- Run `npx wc-e2e docker:down` when you are done with running e2e tests and before making any changes to test site configuration.
|
||||
|
||||
Note that running `npx wc-e2e docker:down` and then `npx wc-e2e docker:up` re-initializes the test container.
|
||||
|
||||
|
@ -153,15 +153,15 @@ npx wc-e2e test:e2e
|
|||
|
||||
### How to run tests in non-headless mode
|
||||
|
||||
Tests are run headless by default. However, sometimes it's useful to observe the browser while running tests. To do so, you can run tests in a non-headless (dev) mode:
|
||||
Tests run in headless mode by default. However, sometimes it's useful to observe the browser while running or developing tests. To do so, you can run tests in a non-headless (dev) mode:
|
||||
|
||||
```bash
|
||||
npx wc-e2e test:e2e-dev
|
||||
```
|
||||
|
||||
The dev mode also enables SlowMo mode. SlowMo slows down Puppeteer’s operations so we can better see what is happening in the browser.
|
||||
The dev mode also enables SlowMo mode. SlowMo slows down Puppeteer’s operations. This makes it easier to see what is happening in the browser.
|
||||
|
||||
By default, SlowMo mode is set to slow down running of tests by 50 milliseconds. If you'd like to override it and have the tests run faster or slower in the `-dev` mode, pass `PUPPETEER_SLOWMO` variable when running tests as shown below:
|
||||
By default, SlowMo mode adds a 50 millisecond delay between test steps. If you'd like to override the length of the delay and have the tests run faster or slower in the `-dev` mode, pass `PUPPETEER_SLOWMO` variable when running tests as shown below:
|
||||
|
||||
```
|
||||
PUPPETEER_SLOWMO=10 npx wc-e2e test:e2e-dev
|
||||
|
@ -176,13 +176,13 @@ For example:
|
|||
|
||||
### How to run tests in debug mode
|
||||
|
||||
Tests are run headless by default. While writing tests it may be useful to have the debugger loaded while running a test in non-headless mode. To run tests in debug mode:
|
||||
Tests run in headless mode by default. While writing tests it may be useful to have the debugger loaded while running a test in non-headless mode. To run tests in debug mode:
|
||||
|
||||
```bash
|
||||
npx wc-e2e test:e2e-debug
|
||||
```
|
||||
|
||||
When all tests have been completed the debugger is left active. Control doesn't return to the command line until the debugger is closed. Otherwise, debug mode functions the same as non-headless mode.
|
||||
When all tests have been completed the debugger remains active. Control doesn't return to the command line until the debugger is closed. Otherwise, debug mode functions the same as non-headless mode.
|
||||
|
||||
### How to run an individual test
|
||||
|
||||
|
@ -240,7 +240,7 @@ describe.skip( 'Store owner can go through store Setup Wizard', () => {}
|
|||
|
||||
### How to run tests using custom WordPress, PHP and MariaDB versions
|
||||
|
||||
The following variables can be used to specify the versions of WordPress, PHP and MariaDB that you'd like to use to built your test site with Docker:
|
||||
The following variables can be used to specify the versions of WordPress, PHP and MariaDB that you'd like to use to build your test site with Docker:
|
||||
|
||||
- `WP_VERSION`
|
||||
- `TRAVIS_PHP_VERSION`
|
||||
|
@ -249,7 +249,7 @@ The following variables can be used to specify the versions of WordPress, PHP an
|
|||
The full command to build the site will look as follows:
|
||||
|
||||
```
|
||||
TRAVIS_MARIADB_VERSION=10.5.3 TRAVIS_PHP_VERSION=7.4.5 WP_VERSION=5.4.1 npm run docker:up
|
||||
TRAVIS_MARIADB_VERSION=10.5.3 TRAVIS_PHP_VERSION=7.4.5 WP_VERSION=5.4.1 npx wc-e2e docker:up
|
||||
```
|
||||
|
||||
## Guide for writing e2e tests
|
||||
|
@ -262,16 +262,16 @@ We use the following tools to write e2e tests:
|
|||
- [jest-puppeteer](https://github.com/smooth-code/jest-puppeteer) – provides all required configuration to run tests using Puppeteer
|
||||
- [expect-puppeteer](https://github.com/smooth-code/jest-puppeteer/tree/master/packages/expect-puppeteer) – assertion library for Puppeteer
|
||||
|
||||
In the WooCommerce Core repository the tests are kept in `tests/e2e/core-tests/specs/` folder. However, if you are writing tests in your own project using WooCommerce Core e2e packages, the tests should be located in `tests/e2e/specs/` folder.
|
||||
In the WooCommerce Core repository the tests are in `tests/e2e/core-tests/specs/` folder. However, if you are writing tests in your own project using WooCommerce Core e2e packages, the tests should be located in `tests/e2e/specs/` folder.
|
||||
|
||||
The following packages are used to write tests:
|
||||
The following packages are used in write tests:
|
||||
|
||||
- `@automattic/puppeteer-utils` - utilities and configuration for running puppeteer against WordPress. See details in the [package's repository](https://github.com/Automattic/puppeteer-utils).
|
||||
- `@woocommerce/e2e-utils` - this package contains utilities to simplify writing e2e tests specific to WooCommmerce. See details in the [package's repository](https://github.com/woocommerce/woocommerce/tree/master/tests/e2e/utils).
|
||||
|
||||
### Creating test structure
|
||||
|
||||
It is a good practice to start working on the test by identifying what needs to be tested on the higher and lower levels. For example, if you are writing a test to verify that merchant can create virtual product, the overview of the test will be as follows:
|
||||
It is a good practice to start working on the test by identifying what needs to be tested on the higher and lower levels. For example, if you are writing a test to verify that merchant can create a virtual product, the overview of the test will be as follows:
|
||||
|
||||
- Merchant can create virtual product
|
||||
- Merchant can log in
|
||||
|
@ -305,23 +305,23 @@ describe( 'Merchant can create virtual product', () => {
|
|||
} );
|
||||
```
|
||||
|
||||
Next, you can start filling up each section with relevant functions (test building blocks). Note, that we have the `@woocommerce/e2e-utils` package where many reusable helper functions can be found for writing tests. For example, `flows.js` of `@woocommerce/e2e-utils` package contains `StoreOwnerFlow` object that has `login` method. As a result, in the test it can be used as `await StoreOwnerFlow.login();` so the first `it()` section of the test will become:
|
||||
Next, you can start filling up each section with relevant functions (test building blocks). Note, that we have the `@woocommerce/e2e-utils` package where many reusable helper functions can be found for writing tests. For example, `flows.js` of `@woocommerce/e2e-utils` package contains `merchant` object that has `login` method. As a result, in the test it can be used as `await merchant.login();` so the first `it()` section of the test will become:
|
||||
|
||||
```
|
||||
it( 'merchant can log in', async () => {
|
||||
await StoreOwnerFlow.login();
|
||||
await merchant.login();
|
||||
} );
|
||||
```
|
||||
|
||||
Moving to the next section where we need to actually create a product. You will find that we have a reusable function such as `createSimpleProduct()` in the `components.js` of `@woocommerce/e2e-utils` package. However, note that this function should not be used for this test because the way simple product is being created in this function is by using WooCommerce REST API. Because this is not how the merchant would typically create a virtual product, we would need to test it by writing actual steps for creating a product in the test.
|
||||
Moving to the next section where we need to actually create a product. You will find that we have a reusable function such as `createSimpleProduct()` in the `components.js` of `@woocommerce/e2e-utils` package. However, note that this function should not be used for testing creating a product because the simple product is created using WooCommerce REST API. This is not how the merchant would typically create a virtual product, we would need to test it by writing actual steps for creating a product in the test.
|
||||
|
||||
`createSimpleProduct()` should be used in tests where you need to test something else than creating a simple product. In other words, this function exists in order to quickly fill the site with test data required for running tests. For example, if you want to write a test that will verify that shopper can place a product to the cart on the site, you can use `createSimpleProduct()` to create a product to test the cart.
|
||||
|
||||
Because `createSimpleProduct()` can't be used in the case of our example test, we'd need to navigate to the page where the user would usually create a product. To do that, there is `openNewProduct()` function of the `StoreOwnerFlow` object that we already used above. As a result, that part of the test will look as follows:
|
||||
Because `createSimpleProduct()` can't be used in the case of our example test, we'd need to navigate to the page where the user would usually create a product. To do that, there is `openNewProduct()` function of the `merchant` object that we already used above. As a result, that part of the test will look as follows:
|
||||
|
||||
```
|
||||
it( 'merchant can create virtual product', async () => {
|
||||
await StoreOwnerFlow.openNewProduct();
|
||||
await merchant.openNewProduct();
|
||||
} );
|
||||
```
|
||||
|
||||
|
@ -335,7 +335,7 @@ await waitAndClick( page, '#selector' );
|
|||
|
||||
### Best practices
|
||||
|
||||
- It is best to keep the tests inside `describe()` block granular as it helps to debug the test if it fails. When the test is done running, you will see the result along with the breakdown of how each of the test sections performed. If one of the tests within `describe()` block fails, it will be shown as follows:
|
||||
- It is best to keep the tests inside `describe()` block granular as it helps to debug the test if it fails. When the test has finished, you will see the result along with the breakdown of how each of the test sections performed. If one of the tests within `describe()` block fails, it will be shown as follows:
|
||||
|
||||
```
|
||||
FAIL ../specs/front-end/front-end-my-account.test.js (9.219s)
|
||||
|
|
|
@ -61,7 +61,9 @@ The functions to access the core tests are:
|
|||
### Shopper
|
||||
|
||||
- `runShopperTests` - Run all shopper tests
|
||||
- `runCartApplyCouponsTest` - Shopper can use coupons on cart
|
||||
- `runCartPageTest` - Shopper can view and update cart
|
||||
- `runCheckoutApplyCouponsTest` - Shopper can use coupons on checkout
|
||||
- `runCheckoutPageTest` - Shopper can complete checkout
|
||||
- `runMyAccountPageTest` - Shopper can access my account page
|
||||
- `runSingleProductPageTest` - Shopper can view single product page in many variations (simple, variable, grouped)
|
||||
|
|
|
@ -7,7 +7,8 @@ const {
|
|||
merchant,
|
||||
createCoupon,
|
||||
createSimpleProduct,
|
||||
uiUnblocked
|
||||
uiUnblocked,
|
||||
clearAndFillInput,
|
||||
} = require( '@woocommerce/e2e-utils' );
|
||||
|
||||
/**
|
||||
|
@ -19,11 +20,34 @@ const {
|
|||
beforeAll,
|
||||
} = require( '@jest/globals' );
|
||||
|
||||
/**
|
||||
* Apply a coupon code to the cart.
|
||||
*
|
||||
* @param couponCode string
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const applyCouponToCart = async ( couponCode ) => {
|
||||
await clearAndFillInput('#coupon_code', couponCode);
|
||||
await expect(page).toClick('button', {text: 'Apply coupon'});
|
||||
await uiUnblocked();
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove one coupon from the cart.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const removeCouponFromCart = async () => {
|
||||
await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'});
|
||||
await uiUnblocked();
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon has been removed.'});
|
||||
}
|
||||
const runCartApplyCouponsTest = () => {
|
||||
describe('Cart applying coupons', () => {
|
||||
let couponFixedCart;
|
||||
let couponPercentage;
|
||||
let couponFixedProduct;
|
||||
let couponFixedCart;
|
||||
let couponPercentage;
|
||||
let couponFixedProduct;
|
||||
|
||||
beforeAll(async () => {
|
||||
await merchant.login();
|
||||
await createSimpleProduct();
|
||||
|
@ -31,76 +55,68 @@ const runCartApplyCouponsTest = () => {
|
|||
couponPercentage = await createCoupon('50', 'Percentage discount');
|
||||
couponFixedProduct = await createCoupon('5', 'Fixed product discount');
|
||||
await merchant.logout();
|
||||
});
|
||||
|
||||
it('allows customer to apply coupons in the cart', async () => {
|
||||
await shopper.goToShop();
|
||||
await shopper.addToCartFromShopPage('Simple product');
|
||||
await shopper.goToCart();
|
||||
await shopper.productIsInCart('Simple product');
|
||||
|
||||
// Apply Fixed cart discount coupon
|
||||
await expect(page).toFill('#coupon_code', couponFixedCart);
|
||||
await expect(page).toClick('button', {text: 'Apply coupon'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
await shopper.goToCart();
|
||||
});
|
||||
|
||||
// Wait for page to expand total calculations to avoid flakyness
|
||||
await page.waitForSelector('.order-total');
|
||||
it('allows customer to apply fixed cart coupon', async () => {
|
||||
await applyCouponToCart( couponFixedCart );
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
|
||||
// Verify discount applied and order total
|
||||
await page.waitForSelector('.cart-discount .amount', {text: '$5.00'});
|
||||
await page.waitForSelector('.order-total .amount', {text: '$4.99'});
|
||||
await page.waitForSelector('.order-total');
|
||||
await expect(page).toMatchElement('.cart-discount .amount', {text: '$5.00'});
|
||||
await expect(page).toMatchElement('.order-total .amount', {text: '$4.99'});
|
||||
await removeCouponFromCart();
|
||||
});
|
||||
|
||||
// Remove coupon
|
||||
await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'});
|
||||
it('allows customer to apply percentage coupon', async () => {
|
||||
await applyCouponToCart( couponPercentage );
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
|
||||
// Apply Percentage discount coupon
|
||||
await expect(page).toFill('#coupon_code', couponPercentage);
|
||||
await expect(page).toClick('button', {text: 'Apply coupon'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
await page.waitForSelector('.cart-discount .amount', {text: '$4.99'});
|
||||
await page.waitForSelector('.order-total .amount', {text: '$5.00'});
|
||||
// Verify discount applied and order total
|
||||
await page.waitForSelector('.order-total');
|
||||
await expect(page).toMatchElement('.cart-discount .amount', {text: '$4.99'});
|
||||
await expect(page).toMatchElement('.order-total .amount', {text: '$5.00'});
|
||||
await removeCouponFromCart();
|
||||
});
|
||||
|
||||
// Remove coupon
|
||||
await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'});
|
||||
it('allows customer to apply fixed product coupon', async () => {
|
||||
await applyCouponToCart( couponFixedProduct );
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
|
||||
// Apply Fixed product discount coupon
|
||||
await expect(page).toFill('#coupon_code', couponFixedProduct);
|
||||
await expect(page).toClick('button', {text: 'Apply coupon'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
await page.waitForSelector('.cart-discount .amount', {text: '$5.00'});
|
||||
await page.waitForSelector('.order-total .amount', {text: '$4.99'});
|
||||
// Verify discount applied and order total
|
||||
await page.waitForSelector('.order-total');
|
||||
await expect(page).toMatchElement('.cart-discount .amount', {text: '$5.00'});
|
||||
await expect(page).toMatchElement('.order-total .amount', {text: '$4.99'});
|
||||
await removeCouponFromCart();
|
||||
});
|
||||
|
||||
// Try to apply the same coupon
|
||||
await expect(page).toFill('#coupon_code', couponFixedProduct);
|
||||
await expect(page).toClick('button', {text: 'Apply coupon'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.woocommerce-error', { text: 'Coupon code already applied!' });
|
||||
it('prevents customer applying same coupon twice', async () => {
|
||||
await applyCouponToCart( couponFixedCart );
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
await applyCouponToCart( couponFixedCart );
|
||||
// Verify only one discount applied
|
||||
// This is a work around for Puppeteer inconsistently finding 'Coupon code already applied'
|
||||
await expect(page).toMatchElement('.cart-discount .amount', {text: '$5.00'});
|
||||
await expect(page).toMatchElement('.order-total .amount', {text: '$4.99'});
|
||||
});
|
||||
|
||||
// Try to apply multiple coupons
|
||||
await expect(page).toFill('#coupon_code', couponFixedCart);
|
||||
await expect(page).toClick('button', {text: 'Apply coupon'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
await page.waitForSelector('.order-total .amount', {text: '$0.00'});
|
||||
it('allows customer to apply multiple coupons', async () => {
|
||||
await applyCouponToCart( couponFixedProduct );
|
||||
await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
|
||||
// Remove coupon
|
||||
await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'});
|
||||
await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'});
|
||||
// Verify discount applied and order total
|
||||
await page.waitForSelector('.order-total');
|
||||
await expect(page).toMatchElement('.order-total .amount', {text: '$0.00'});
|
||||
});
|
||||
|
||||
// Verify the total amount after all coupons removal
|
||||
await page.waitForSelector('.order-total .amount', {text: '$9.99'});
|
||||
it('restores cart total when coupons are removed', async () => {
|
||||
await removeCouponFromCart();
|
||||
await removeCouponFromCart();
|
||||
await expect(page).toMatchElement('.order-total .amount', {text: '$9.99'});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -79,8 +79,8 @@ You can override these in `/tests/e2e/config/default.json`.
|
|||
|
||||
The built in container defaults to mapping the root folder of the repository to a folder in the `plugins` folder. For example `woocommerce` is mapped to `/var/www/html/wp-content/plugins/woocommerce`. Use the `WC_E2E_FOLDER_MAPPING` environment variable to override this mapping.
|
||||
|
||||
- Storefront Theme - ```WC_E2E_FOLDER_MAPPING=/var/www/html/wp-content/themes/storefront npm explore @woocommerce/e2e-environment -- npm run docker:up```
|
||||
- Site Project - ```WC_E2E_FOLDER_MAPPING=/var/www/html/wp-content/plugins npm explore @woocommerce/e2e-environment -- npm run docker:up```
|
||||
- Storefront Theme - ```WC_E2E_FOLDER_MAPPING=/var/www/html/wp-content/themes/storefront npx wc-e2e docker:up```
|
||||
- Site Project - ```WC_E2E_FOLDER_MAPPING=/var/www/html/wp-content npx wc-e2e docker:up```
|
||||
|
||||
### Travis CI Supported Versions
|
||||
|
||||
|
@ -106,11 +106,11 @@ version: ~> 1.0
|
|||
|
||||
script:
|
||||
- npm install jest --global
|
||||
- npm explore @woocommerce/e2e-environment -- npm run docker:up
|
||||
- npm explore @woocommerce/e2e-environment -- npm run test:e2e
|
||||
- npx wc-e2e docker:up
|
||||
- npx wc-e2e test:e2e
|
||||
|
||||
....
|
||||
|
||||
after_script:
|
||||
- npm explore @woocommerce/e2e-environment -- npm run docker:down
|
||||
- npx wc-e2e docker:down
|
||||
```
|
||||
|
|
Loading…
Reference in New Issue