diff --git a/plugins/woocommerce/.wp-env.json b/plugins/woocommerce/.wp-env.json index 4c11d22dc4a..facd8d58499 100644 --- a/plugins/woocommerce/.wp-env.json +++ b/plugins/woocommerce/.wp-env.json @@ -1,6 +1,8 @@ { "phpVersion": "7.4", - "plugins": [ "." ], + "plugins": [ + "." + ], "config": { "JETPACK_AUTOLOAD_DEV": true, "WP_DEBUG_LOG": true, @@ -11,7 +13,8 @@ "wp-cli.yml": "./tests/wp-cli.yml", "wp-content/plugins/filter-setter.php": "./tests/e2e-pw/bin/filter-setter.php", "wp-content/plugins/process-waiting-actions.php": "./tests/e2e-pw/bin/process-waiting-actions.php", - "wp-content/plugins/test-helper-apis.php": "./tests/e2e-pw/bin/test-helper-apis.php" + "wp-content/plugins/test-helper-apis.php": "./tests/e2e-pw/bin/test-helper-apis.php", + "test-data/images/": "./tests/e2e-pw/test-data/images/" }, "lifecycleScripts": { "afterStart": "./tests/e2e-pw/bin/test-env-setup.sh", diff --git a/plugins/woocommerce/changelog/e2e-product-images-tests b/plugins/woocommerce/changelog/e2e-product-images-tests new file mode 100644 index 00000000000..d33df9ca10e --- /dev/null +++ b/plugins/woocommerce/changelog/e2e-product-images-tests @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +E2E tests: Add checks for product images diff --git a/plugins/woocommerce/tests/e2e-pw/bin/test-env-setup.sh b/plugins/woocommerce/tests/e2e-pw/bin/test-env-setup.sh index 864b2dd24f0..a6bca1b4820 100755 --- a/plugins/woocommerce/tests/e2e-pw/bin/test-env-setup.sh +++ b/plugins/woocommerce/tests/e2e-pw/bin/test-env-setup.sh @@ -37,3 +37,7 @@ if [ $ENABLE_TRACKING == 1 ]; then echo -e 'Enable tracking\n' wp-env run tests-cli wp option update woocommerce_allow_tracking 'yes' fi + +echo -e 'Upload test images \n' +wp-env run tests-cli wp media import './test-data/images/image-01.png' './test-data/images/image-02.png' './test-data/images/image-03.png' + diff --git a/plugins/woocommerce/tests/e2e-pw/playwright.config.js b/plugins/woocommerce/tests/e2e-pw/playwright.config.js index 777d117f759..35b09fc1de5 100644 --- a/plugins/woocommerce/tests/e2e-pw/playwright.config.js +++ b/plugins/woocommerce/tests/e2e-pw/playwright.config.js @@ -8,6 +8,7 @@ const { DEFAULT_TIMEOUT_OVERRIDE, E2E_MAX_FAILURES, PLAYWRIGHT_HTML_REPORT, + REPEAT_EACH, } = process.env; const config = { @@ -20,6 +21,7 @@ const config = { globalTeardown: require.resolve( './global-teardown' ), testDir: 'tests', retries: 2, + repeatEach: REPEAT_EACH ? Number( REPEAT_EACH ) : 1, workers: CI ? 1 : 4, reporter: [ [ 'list' ], diff --git a/plugins/woocommerce/tests/e2e-pw/test-data/images/image-01.png b/plugins/woocommerce/tests/e2e-pw/test-data/images/image-01.png new file mode 100644 index 00000000000..739672bdbf0 Binary files /dev/null and b/plugins/woocommerce/tests/e2e-pw/test-data/images/image-01.png differ diff --git a/plugins/woocommerce/tests/e2e-pw/test-data/images/image-02.png b/plugins/woocommerce/tests/e2e-pw/test-data/images/image-02.png new file mode 100644 index 00000000000..935a794ae8e Binary files /dev/null and b/plugins/woocommerce/tests/e2e-pw/test-data/images/image-02.png differ diff --git a/plugins/woocommerce/tests/e2e-pw/test-data/images/image-03.png b/plugins/woocommerce/tests/e2e-pw/test-data/images/image-03.png new file mode 100644 index 00000000000..bdb9cb39c84 Binary files /dev/null and b/plugins/woocommerce/tests/e2e-pw/test-data/images/image-03.png differ diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/product-delete.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/product-delete.spec.js index bd237c87446..6dc18334b83 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/product-delete.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/product-delete.spec.js @@ -4,7 +4,7 @@ const wcApi = require('@woocommerce/woocommerce-rest-api').default; baseTest.describe('Products > Delete Product', () => { baseTest.use({storageState: process.env.ADMINSTATE}); - const apiFixture = baseTest.extend({ + const test = baseTest.extend({ api: async ({baseURL}, use) => { const api = new wcApi({ url: baseURL, @@ -21,9 +21,7 @@ baseTest.describe('Products > Delete Product', () => { await use(api); }, - }); - const test = apiFixture.extend({ product: async ({api}, use) => { const product = { id: 0, diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/product-images.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/product-images.spec.js new file mode 100644 index 00000000000..9310f02c565 --- /dev/null +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/product-images.spec.js @@ -0,0 +1,285 @@ +const { test: baseTest, expect } = require( '@playwright/test' ); +const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; + +async function addImageFromLibrary( page, imageName, actionButtonName ) { + await page.getByRole( 'tab', { name: 'Media Library' } ).click(); + await page.getByRole( 'searchbox', { name: 'Search' } ).fill( imageName ); + const imageLocator = page.getByLabel( imageName ).nth( 0 ); + await imageLocator.click(); + const dataId = await imageLocator.getAttribute( 'data-id' ); + await expect( imageLocator ).toBeChecked(); + await page.getByRole( 'button', { name: actionButtonName } ).click(); + return dataId; +} + +baseTest.describe( 'Products > Product Images', () => { + baseTest.use( { storageState: process.env.ADMINSTATE } ); + + const test = baseTest.extend( { + api: async ( { baseURL }, use ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + + await use( api ); + }, + product: async ( { api }, use ) => { + let product = { + id: 0, + name: `Product ${ Date.now() }`, + type: 'simple', + regular_price: '12.99', + sale_price: '11.59', + }; + + await api.post( 'products', product ).then( ( response ) => { + product = response.data; + } ); + + await use( product ); + + // Cleanup + await api.delete( `products/${ product.id }`, { force: true } ); + }, + productWithImage: async ( { api, product }, use ) => { + let productWithImage; + await api + .put( `products/${ product.id }`, { + images: [ + { + src: 'http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_2_front.jpg', + }, + ], + } ) + .then( ( response ) => { + productWithImage = response.data; + } ); + + await use( productWithImage ); + }, + productWithGallery: async ( { api, product }, use ) => { + let productWithGallery; + await api + .put( `products/${ product.id }`, { + images: [ + { + src: 'http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_2_front.jpg', + }, + { + src: 'http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_2_back.jpg', + }, + { + src: 'http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_3_front.jpg', + }, + ], + } ) + .then( ( response ) => { + productWithGallery = response.data; + } ); + + await use( productWithGallery ); + }, + } ); + + test( 'can set product image', async ( { page, product } ) => { + await test.step( 'Navigate to product edit page', async () => { + await page.goto( + `wp-admin/post.php?post=${ product.id }&action=edit` + ); + } ); + + await test.step( 'Set product image', async () => { + await page + .getByRole( 'link', { name: 'Set product image' } ) + .click(); + await addImageFromLibrary( page, 'image-01', 'Set product image' ); + + // Wait for the product image thumbnail to be updated. + // Clicking the "Update" button before this happens will not update the image. + // Use src* (contains) instead of src$ (ends with) to match duplicated images, like image-01-1.png + await expect( + page.locator( '#set-post-thumbnail img[src*="image-01"]' ) + ).toBeVisible(); + + await page.getByRole( 'button', { name: 'Update' } ).click(); + } ); + + await test.step( 'Verify product image was set', async () => { + // Verify product was updated + await expect( page.getByText( 'Product updated.' ) ).toBeVisible(); + + // Verify image in store frontend + await page.goto( product.permalink ); + await expect( page.getByTitle( `image-01` ) ).toBeVisible(); + } ); + } ); + + test( 'can update the product image', async ( { + page, + productWithImage, + } ) => { + await test.step( 'Navigate to product edit page', async () => { + await page.goto( + `wp-admin/post.php?post=${ productWithImage.id }&action=edit` + ); + } ); + + await test.step( 'Update product image', async () => { + await page.locator( '#set-post-thumbnail' ).click(); + await addImageFromLibrary( page, 'image-02', 'Set product image' ); + + // Wait for the product image thumbnail to be updated. + // Clicking the "Update" button before this happens will not update the image. + // Use src* (contains) instead of src$ (ends with) to match duplicated images, like image-01-1.png + await expect( + page.locator( '#set-post-thumbnail img[src*="image-02"]' ) + ).toBeVisible(); + + await page.getByRole( 'button', { name: 'Update' } ).click(); + } ); + + await test.step( 'Verify product image was set', async () => { + // Verify product was updated + await expect( page.getByText( 'Product updated.' ) ).toBeVisible(); + + // Verify image in store frontend + await page.goto( productWithImage.permalink ); + await expect( page.getByTitle( `image-02` ) ).toBeVisible(); + } ); + } ); + + test( 'can delete the product image', async ( { + page, + productWithImage, + } ) => { + await test.step( 'Navigate to product edit page', async () => { + await page.goto( + `wp-admin/post.php?post=${ productWithImage.id }&action=edit` + ); + } ); + + await test.step( 'Remove product image', async () => { + await page + .getByRole( 'link', { name: 'Remove product image' } ) + .click(); + await expect( + page.getByRole( 'link', { name: 'Set product image' } ) + ).toBeVisible(); + + await page.getByRole( 'button', { name: 'Update' } ).click(); + } ); + + await test.step( 'Verify product image was removed', async () => { + // Verify product was updated + await expect( page.getByText( 'Product updated.' ) ).toBeVisible(); + + // Verify image in store frontend + await page.goto( productWithImage.permalink ); + await expect( + page.getByAltText( 'Awaiting product image' ) + ).toBeVisible(); + } ); + } ); + + test( 'can create a product gallery', async ( { + page, + productWithImage, + } ) => { + const images = [ 'image-02', 'image-03' ]; + + await test.step( 'Navigate to product edit page', async () => { + await page.goto( + `wp-admin/post.php?post=${ productWithImage.id }&action=edit` + ); + } ); + + await test.step( 'Add product gallery images', async () => { + const imageSelector = '#product_images_container img'; + let initialImagesCount = await page + .locator( imageSelector ) + .count(); + + for ( const image of images ) { + await page + .getByRole( 'link', { name: 'Add product gallery images' } ) + .click(); + const dataId = await addImageFromLibrary( + page, + image, + 'Add to gallery' + ); + + await expect( + page.locator( `li[data-attachment_id="${ dataId }"]` ), + 'thumbnail should be visible' + ).toBeVisible(); + const currentImagesCount = await page + .locator( imageSelector ) + .count(); + await expect( + currentImagesCount, + 'number of images should increase' + ).toEqual( initialImagesCount + 1 ); + initialImagesCount = currentImagesCount; + } + + await page.getByRole( 'button', { name: 'Update' } ).click(); + } ); + + await test.step( 'Verify product gallery', async () => { + // Verify gallery in store frontend + await page.goto( productWithImage.permalink ); + await expect( + page + .locator( `#product-${ productWithImage.id } ol img` ) + .nth( images.length ), + 'all gallery images should be visible' + ).toBeVisible(); // +1 for the featured image + } ); + } ); + + test( 'can update a product gallery', async ( { + page, + productWithGallery, + } ) => { + let imagesCount; + + await test.step( 'Navigate to product edit page', async () => { + await page.goto( + `wp-admin/post.php?post=${ productWithGallery.id }&action=edit` + ); + } ); + + await test.step( 'Remove images from product gallery', async () => { + const imageSelector = '#product_images_container img'; + imagesCount = await page.locator( imageSelector ).count(); + + await page.locator( imageSelector ).first().hover(); + await page.getByRole( 'link', { name: ' Delete' } ).click(); + + await expect( + await page.locator( imageSelector ).count(), + 'number of images should decrease' + ).toEqual( imagesCount - 1 ); + + await page.getByRole( 'button', { name: 'Update' } ).click(); + } ); + + await test.step( 'Verify product gallery', async () => { + // Verify gallery in store frontend + await page.goto( productWithGallery.permalink ); + const selector = `#product-${ productWithGallery.id } ol img`; + await expect( + page.locator( selector ).nth( imagesCount - 1 ), + 'gallery images should be visible' + ).toBeVisible(); + await expect( + page.locator( selector ).nth( imagesCount ), + 'one gallery image should not be visible' + ).toBeHidden(); + } ); + } ); +} );