From d66a48e8e599f36123f740f4a2d907194c408255 Mon Sep 17 00:00:00 2001 From: Adrian Moldovan <3854374+adimoldovan@users.noreply.github.com> Date: Tue, 23 Jan 2024 23:31:22 +0200 Subject: [PATCH] [e2e tests] Add tests for product images (#43775) * Import media on environment setup * Add product-images.spec with one complete test and all other skeleton tests * Add changelog * Implement can update the product image * Implement can delete the product image * Implement can delete the product image * Skip not implemented test * Merge fixtures * Add productWithImage fixture * Remove unused variable `api` * Partial implementation of can create a product gallery * Check the image gallery in store frontend * Added `can update a product gallery` test * Run all tests * Search for image in media library * Check that the remove gallery image is not displayed * Add repeatEach configuration * eslint formatting * Extract some repeating steps into a function * Update check for thumbnail * Fix test image name --- plugins/woocommerce/.wp-env.json | 7 +- .../changelog/e2e-product-images-tests | 4 + .../tests/e2e-pw/bin/test-env-setup.sh | 4 + .../tests/e2e-pw/playwright.config.js | 2 + .../e2e-pw/test-data/images/image-01.png | Bin 0 -> 9048 bytes .../e2e-pw/test-data/images/image-02.png | Bin 0 -> 1424 bytes .../e2e-pw/test-data/images/image-03.png | Bin 0 -> 1175 bytes .../tests/merchant/product-delete.spec.js | 4 +- .../tests/merchant/product-images.spec.js | 285 ++++++++++++++++++ 9 files changed, 301 insertions(+), 5 deletions(-) create mode 100644 plugins/woocommerce/changelog/e2e-product-images-tests create mode 100644 plugins/woocommerce/tests/e2e-pw/test-data/images/image-01.png create mode 100644 plugins/woocommerce/tests/e2e-pw/test-data/images/image-02.png create mode 100644 plugins/woocommerce/tests/e2e-pw/test-data/images/image-03.png create mode 100644 plugins/woocommerce/tests/e2e-pw/tests/merchant/product-images.spec.js 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 0000000000000000000000000000000000000000..739672bdbf083ad6db9b939b84f63496606b7efa GIT binary patch literal 9048 zcmeHLc~DdL7XBpx!fFZxWe=+aku5BSRnQVyiWo!?3Pcn^u!6{;v(10L+B|H0V zO7=T!wkbv6^)xps`R@1g3I8rkDJ0zIyMVArVWggLA8e%j|EbusU9Row$%1dBELPDY< zqS9hw(l}*#W!!&#Kvh6o5WX9}8v#=RaB&zy90t__3;-~GF54Btza20*f{$MSDJUc? z!rf5(6@bGK2sj^tpP!G9yE}&a9Po+rODOAG3rG@ukSZZ)+=(A<394=>dMxGKKBZ=` z_sB^h;kE0eWn|UYYhX1u7#bOG!kd_G{o01G&DPF-r^_x^x83d@`?$gKKM)WY8WtXL z^jKunsnciT6V9HaB>#Bna!TrzpRT57Wd59$os*k)@BV}0lG2AXdUZ{0-IMx;#-?W- z&pW%idtUSoz8QKuJTl4{8)r^`oSB`QXDuwQ@PYxvKWY8WY!xqYE-yGAAA%3L!V3mJ z$^}B4k6&3|K*E}c^a+tv!JQC9Z~5U?(PJT11LrBJy+_)G*Qyx~sxw!p{mSg`5j*)m zG5ej^zj^fnQ3Q;eJcKyd3}$+<>1C2ngt6&yJn&+`(*REcJPmwd8qm7VJbGcK64@hE zi1~%~J!bt2RI4)Q)WAabS+8X;W@26cy~jEAha`<%rQZ+jw`PcFYA}u39mJgT=sjI& zGLBvtJ0i&d0@dBjQno(`O+ad4t+Bx zuCuY*eX5ybZ=47LFAM~%0$WO_h8c~yQwC#;*z}6y(_1LmoAA#HEy-HWh7NPhtZU_j zJ<_L!tulizEn+$TH?yT_X3Cj;_YA|u2(9)-DRZ1RX3+=Yy|>{J%TC3|A;1l4H+4NO zU@>CJO9QWpk7W0?k?${zmY~j;1>_y8hQQM@vQ8)J!y#gYZT)O8dH5NvY4$G51{D>Z z_B;nWpiRV)@wOoo6nEpjxpGfCJ29idpA`_!;h z(!9fV+WwvU%bt|IQOQm_zbW*KS2RqyMm=L{fAym8SVno)BBb%~a!F>$g%qP( zg9BpY-q`0|^^T{aXCviKC&uia;}p-3PMM2ju`HXv%As;>YP-LyG>F!nckBE3bOYg)Nl*+0G@^Fv)V~k)u8& z<7F=hBqzo|!1E0R+W2GC|F9IkXETK@HdQH8zrGNYd>GIE~| zS7!&t;ad8?&oiTqP{AgG%hKbb1mxg zsFBzE`jIofJ1}PkPSeL1qax(4xYlCF6q-Cx2{Qv?0q1Nm?UbbJM!!y6%(1on6OqDirK$d1YVJSmyB(zsf zWI%u+=A9Ni*xa60Y$*`4*#_;_O11q=?FcqPmM6cpzYl@C6f%RRRi!n+!9$?ovOffT z&=6=p;ub$<>NrQssq@MH_Q5=kDiw`#j)sjN?0IaM^O72`(EF1sHk0Z?k?ysVKjUJZ zTssHC3xlSR)xJggfv;2(eQDAM@>_)KKX9`9Zcb8PqE1-K^{}?9KcaE_oZdoKee>FA zrOmCmbMkJXo;}z1r9N`nC);y=sfb&=9a4o$z5SBO6DO#hgYS?rN_#7CD zRXetKzK*UQe{C;Onv|K`FnYF4_2et|HpSW8!C=9$Sc#5`W*r+ST4d?1@uX?ugWyF51Qro)8eaZ2x(nFeP3$l-8+Nv*&O2WMa;~f-x=2haAecI(C>Zxl8EXi)7xhwL*zj z%}RBjytL1GI--qHW2ZSDDfQ`RKrRFZU5@o};wl%6P!L$>g*p+dz`9LfW?jo6jm0|V z>pZcuY?s`F_G#0%uq`AM1O`diI|Pk-j$7A_ulz{#mKotk6RmjQ#ek;)o(6at;A!CR JXaEDf_y;D==@tM0 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..935a794ae8e17a96b4590dbc406676f9024bb792 GIT binary patch literal 1424 zcmZ`(X;2af6h_OX)vXs{m$XbJ=X03d(l zxiA1gHxdBQDc!k4lc7O&@HGw+8W`@oT&fL6@+w3G8uMZb1&O}s>;*q+XSN^gErnNV zYP-{sSP}qW*sWEa3YZ}T0MLW@pYsln7!JFZi%(Ja_y3XA6Dsj%F zSsM6v4;%a({SRQj9|XD5n(=qm;Y1wz|}%>^_gX& zW5jGnft1_g{J@uV%4|9nO1QkuxBMSaGdB7Z(+Hbe8i`zUCF^zpkP!P z7wnPR3p8gP68JB86~>EZ&QLk@NnCWRq;uS@#Jf@nooR`n^3b(^LWQb~2bJF@UcMzl zdym}9ss)SXxCO^ToRH37cLYm;=6mbYlQ)f8(@4e>PnLTHv&J{0%D~oG@KL^`NFgddL_)%#if7|MG+tPXmK@qO*S;#dFzwS zrd_8kv{i91D7y6&9IpYUw4q)&Nr46+YUG6L>4)IQ+`&7OQrGSvvSEf3X`~J1(&d;` z0iz^VpKX1XL$bbLcSjpSenpQ)2?#TZ35`E zY5GV|{WFVrZkm3;>&2ig-I3tl`wh*a(c#jcc)RPb`C#qrLHOrf2{5208-(|=vv=p5 zprb9oOinP?p`k$f>mHiqPCq@-AchI-HvlIB`T3GNs7UY?AdG;*{bk#5nvTW^nh zT(gYv*4s#tH-@OQ**itd>RX{|!sHlFQ`X0tUV-I&C)|9T!5mzz!L<{I7>78mAl2}1 zGUqwJyP1ulb*BBkiJlGlmttQo<=9e}qPAsO)CzyLAu|EO zEGME(K5WOTX`$vbQ)!!?JdN< z+t%_6rDEJq=|l6G9j%*ZxsP86MZMPgQuO?#dfu|>Kw9@zMBIR*o|gc#D2nTq)FW-M1jK#IK%M+=)!4uc9LmT(_Vh zHD8zGCR*3h8U_RVF@GB#3nbKA(8T-{>&e6^ZDF8lMQIBY%*3Up#j3f}J_Agxq!#Q- zTNr?9y`t6~=mUp5p4I?3ok~lXU{2L8W?|z4mw})~^R={vK_7BK=1d^dmM^jKR?QCP zhd6+Yc(uI>H&J&WLrZOM!6nlq;NePB4|6XtDdU50(i&?tL=G{k(1!(JgXNA82g}t* z>sZA@df@*@S|r00eO}#)q)k@vPLx#Xsh(}TcR>nFV6}V8hMVxCtc(9PrgwBS9Iv)O zgwkWx406~Mud?#APNk*tv@XPl%E{4s7)dM5F-I#z7_Quqmlw;`3c>?OE%A9eO{d4_ zvkFy=Sq4x=yQ+ zw8$FzJ&JVdmZ3Hwz*ey7{+8{Y#cH+zbLt$GRwHSRb<}3^p(q`yI?AKZ4ArpxbT@#M3(+T6n))nLrkSDr><_)ar~(x|3@i z^&MIbt!7mHKRfo%R%+FOEODM#tL~vuSH{>qtgcn|_UJQoyUUCD(xKE#H(Ogy@yUv& z?4Go~va*~@6Rnay&V6NNJ83DcqNcXRjZ(HJ9<7ysqm(}iWClxB!-|XD?4v~sco6)y zY_$xYIn%?1nY*{r? 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(); + } ); + } ); +} );