[e2e tests] Add downloadable product test (#44112)

This commit is contained in:
Adrian Moldovan 2024-01-31 17:51:14 +02:00 committed by GitHub
parent a164c8afa9
commit 62101246ca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 374 additions and 342 deletions

View File

@ -733,7 +733,7 @@
"menu_title": "Core critical flows",
"tags": "reference",
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/quality-and-best-practices/core-critical-flows.md",
"hash": "c1380271cea0f15a4be3d2a96b94cfaa0b1b0b098cf717db58640d8f84da1c66",
"hash": "51529e3c07276ea267763d7520f1ca8d8b37eda4337ec97b4c8d19231f3a0334",
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/quality-and-best-practices/core-critical-flows.md",
"id": "e561b46694dba223c38b87613ce4907e4e14333a"
},
@ -1191,5 +1191,5 @@
"categories": []
}
],
"hash": "7e5dbc1e6e4e5191e2ad42a11ded70379385e7c3a923b31d8f4bb717005dee46"
"hash": "f5c8de9fa3200fa1b67e6da03a853285badd8692ce6864949a30715d173b52ec"
}

View File

@ -191,21 +191,21 @@ These flows will continually evolve as the platform evolves with flows updated,
### Merchant - Products
| User Type | Flow Area | Flow Name | Test File |
| --------- | -------------- | ------------------------------ | ---------------------------------------------------------------------- |
|-----------|----------------|--------------------------------|------------------------------------------------------------------------|
| Merchant | Products | View all products | |
| Merchant | Products | Search products | merchant/product-search.spec.js |
| Merchant | Products | Add simple product | merchant/create-simple-product.spec.js |
| Merchant | Products | Add simple product | merchant/product-create-simple.spec.js |
| Merchant | Products | Add variable product | merchant/products/add-variable-product/create-variable-product.spec.js |
| Merchant | Products | Edit product details | merchant/product-edit.spec.js |
| Merchant | Products | Add virtual product | merchant/create-simple-product.spec.js |
| Merchant | Products | Add virtual product | merchant/product-create-simple.spec.js |
| Merchant | Products | Import products CSV | merchant/product-import-csv.spec.js |
| Merchant | Products | Add downloadable product | |
| Merchant | Products | Add downloadable product | merchant/product-create-simple.spec.js |
| Merchant | Products | View product reviews list | |
| Merchant | Products | View all products reviews list | |
| Merchant | Products | Edit product review | |
| Merchant | Products | Trash product review | |
| Merchant | Products | Bulk edit products | |
| Merchant | Products | Remove products | |
| Merchant | Products | Remove products | merchant/product-delete.spec.js |
| Merchant | Products | Manage product images | |
| Merchant | Products | Manage product inventory | |
| Merchant | Products | Manage product attributes | |

View File

@ -0,0 +1,4 @@
Significance: patch
Type: dev
E2E tests: add test for creating a downloadable product

View File

@ -20,7 +20,7 @@ const config = {
globalSetup: require.resolve( './global-setup' ),
globalTeardown: require.resolve( './global-teardown' ),
testDir: 'tests',
retries: 2,
retries: CI ? 2 : 0,
repeatEach: REPEAT_EACH ? Number( REPEAT_EACH ) : 1,
workers: CI ? 1 : 4,
reporter: [
@ -58,7 +58,7 @@ const config = {
maxFailures: E2E_MAX_FAILURES ? Number( E2E_MAX_FAILURES ) : 0,
use: {
baseURL: BASE_URL ?? 'http://localhost:8086',
screenshot: 'only-on-failure',
screenshot: { mode: 'only-on-failure', fullPage: true },
stateDir: 'tests/e2e-pw/test-results/storage/',
trace: 'retain-on-failure',
video: 'on-first-retry',

View File

@ -1,330 +0,0 @@
const { test, expect } = require( '@playwright/test' );
const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default;
const virtualProductName = 'Virtual Product Name';
const nonVirtualProductName = 'Non Virtual Product Name';
const productPrice = '9.99';
const salePrice = '6.99';
const productDescription = 'Virtual product description.';
const productSKU = '1234567890';
const productPurchaseNote = 'Virtual product purchase note';
const productAttribute = 'color';
const productAttributeColor = 'red | white';
const productTag = 'nonVirtualTag';
const productCategory = 'nonVirtualCategory';
const productDescriptionShort = 'Short description';
let shippingZoneId, virtualProductId, nonVirtualProductId;
test.describe.serial( 'Add New Simple Product Page', () => {
test.use( { storageState: process.env.ADMINSTATE } );
test.beforeAll( async ( { baseURL } ) => {
// need to add a shipping zone
const api = new wcApi( {
url: baseURL,
consumerKey: process.env.CONSUMER_KEY,
consumerSecret: process.env.CONSUMER_SECRET,
version: 'wc/v3',
} );
// and the flat rate shipping method to that zone
await api
.post( 'shipping/zones', {
name: 'Somewhere',
} )
.then( ( response ) => {
shippingZoneId = response.data.id;
api.put( `shipping/zones/${ shippingZoneId }/locations`, [
{ code: 'CN' },
] );
api.post( `shipping/zones/${ shippingZoneId }/methods`, {
method_id: 'flat_rate',
} );
} );
} );
test.afterAll( async ( { baseURL } ) => {
const api = new wcApi( {
url: baseURL,
consumerKey: process.env.CONSUMER_KEY,
consumerSecret: process.env.CONSUMER_SECRET,
version: 'wc/v3',
} );
// cleans up all products after run
await api.delete( `products/${ virtualProductId }`, { force: true } );
await api.delete( `products/${ nonVirtualProductId }`, {
force: true,
} );
// clean up tag after run
await api.get( 'products/tags' ).then( async ( response ) => {
for ( let i = 0; i < response.data.length; i++ ) {
if ( response.data[ i ].name === productTag ) {
await api.delete(
`products/tags/${ response.data[ i ].id }`,
{
force: true,
}
);
}
}
} );
// clean up category after run
await api.get( 'products/categories' ).then( async ( response ) => {
for ( let i = 0; i < response.data.length; i++ ) {
if ( response.data[ i ].name === productCategory ) {
await api.delete(
`products/categories/${ response.data[ i ].id }`,
{
force: true,
}
);
}
}
} );
// delete the shipping zone
await api.delete( `shipping/zones/${ shippingZoneId }`, {
force: true,
} );
} );
test( 'can create simple virtual product', async ( { page } ) => {
await page.goto( 'wp-admin/post-new.php?post_type=product', {
waitUntil: 'networkidle',
} );
await page.getByLabel( 'Product name' ).fill( virtualProductName );
await page.getByLabel( 'Regular price' ).fill( productPrice );
await page.getByText( 'Sale price ($)' ).fill( salePrice );
await page.getByText( 'Virtual' ).click();
// Fill in a product description
await page
.getByRole( 'button', { name: 'Visual', exact: true } )
.first()
.click();
await page
.frameLocator( '#content_ifr' )
.locator( '.wp-editor' )
.fill( productDescription );
// Fill in SKU
await page.getByText( 'Inventory' ).click();
await page.getByLabel( 'SKU', { exact: true } ).fill( productSKU );
// Fill in purchase note
await page.getByText( 'Advanced' ).click();
await page.getByLabel( 'Purchase note' ).fill( productPurchaseNote );
await page.keyboard.press( 'Enter' );
// Fill in a color as attribute
await page
.locator( '.attribute_tab' )
.getByRole( 'link', { name: 'Attributes' } )
.click();
await page
.getByPlaceholder( 'f.e. size or color' )
.fill( productAttribute );
await page
.getByPlaceholder(
'Enter some descriptive text. Use “|” to separate different values.'
)
.fill( productAttributeColor );
await page.keyboard.press( 'Enter' );
await page.getByRole( 'button', { name: 'Save attributes' } ).click();
// Publish the product after a short wait
await page
.getByRole( 'button', { name: 'Publish', exact: true } )
.click();
await page.waitForLoadState( 'networkidle' );
// When running in parallel, clicking the publish button sometimes saves products as a draft
if (
(
await page.locator( '#post-status-display' ).innerText()
).includes( 'Draft' )
) {
await page.locator( '#publish' ).click();
await page.waitForLoadState( 'networkidle' );
}
await expect(
page
.locator( 'div.notice-success > p' )
.filter( { hasText: 'Product published.' } )
).toBeVisible();
// Reload the page and verify that the values remain saved after publish in product editor page
await page.reload();
await expect( page.getByLabel( 'Product name' ) ).toHaveValue(
virtualProductName
);
await expect( page.getByLabel( 'Regular price' ) ).toHaveValue(
productPrice
);
await expect( page.getByText( 'Sale price ($)' ) ).toHaveValue(
salePrice
);
await page
.getByRole( 'button', { name: 'Text', exact: true } )
.first()
.click();
await expect( page.getByText( productDescription ) ).toBeVisible();
await page.getByText( 'Inventory' ).click();
await expect( page.getByLabel( 'SKU', { exact: true } ) ).toHaveValue(
productSKU
);
// Save product ID
virtualProductId = page.url().match( /(?<=post=)\d+/ );
expect( virtualProductId ).toBeDefined();
} );
test( 'can have a shopper add the simple virtual product to the cart', async ( {
page,
} ) => {
await page.goto( `/?post_type=product&p=${ virtualProductId }`, {
waitUntil: 'networkidle',
} );
await expect(
page.getByRole( 'heading', { name: virtualProductName } )
).toBeVisible();
await expect( page.getByText( productPrice ).first() ).toBeVisible();
await page.getByRole( 'button', { name: 'Add to cart' } ).click();
await page.getByRole( 'link', { name: 'View cart' } ).click();
await expect( page.locator( 'td[data-title=Product]' ) ).toContainText(
virtualProductName
);
await expect(
page.locator( 'a.shipping-calculator-button' )
).toBeHidden();
await page
.locator( `a.remove[data-product_id='${ virtualProductId }']` )
.click();
await page.waitForLoadState( 'networkidle' );
await expect(
page.locator( `a.remove[data-product_id='${ virtualProductId }']` )
).toBeHidden();
} );
test( 'can create simple non-virtual product', async ( { page } ) => {
await page.goto( 'wp-admin/post-new.php?post_type=product', {
waitUntil: 'networkidle',
} );
await page.getByLabel( 'Product name' ).fill( nonVirtualProductName );
await page
.frameLocator( '#content_ifr' )
.locator( '.wp-editor' )
.fill( productDescription );
await page
.getByRole( 'textbox', { name: 'Regular price ($)', exact: true } )
.fill( productPrice );
await page.getByText( 'Inventory' ).click();
await page.getByLabel( 'SKU', { exact: true } ).fill( '11' );
const productDimensions = {
weight: '2',
length: '20',
width: '10',
height: '30',
};
await page.getByRole( 'link', { name: 'Shipping' } ).click();
await page.getByPlaceholder( '0' ).fill( productDimensions.weight );
await page
.getByPlaceholder( 'Length' )
.fill( productDimensions.length );
await page.getByPlaceholder( 'Width' ).fill( productDimensions.width );
await page
.getByPlaceholder( 'Height' )
.fill( productDimensions.height );
await page
.frameLocator( '#excerpt_ifr' )
.locator( '.wp-editor' )
.fill( productDescriptionShort );
await page.getByText( '+ Add new category' ).click();
await page
.getByLabel( 'Add new category', { exact: true } )
.fill( productCategory );
await page
.getByRole( 'button', { name: 'Add new category', exact: true } )
.click();
await page
.getByRole( 'combobox', { name: 'Add new tag' } )
.fill( productTag );
await page.getByRole( 'button', { name: 'Add', exact: true } ).click();
await page
.getByRole( 'button', {
name: 'Publish',
exact: true,
disabled: false,
} )
.click();
await page.waitForLoadState( 'networkidle' );
// When running in parallel, clicking the publish button sometimes saves products as a draft
if (
(
await page.locator( '#post-status-display' ).innerText()
).includes( 'Draft' )
) {
await page
.getByRole( 'button', {
name: 'Publish',
exact: true,
disabled: false,
} )
.click();
await page.waitForLoadState( 'networkidle' );
}
await expect(
page
.locator( 'div.notice-success > p' )
.filter( { hasText: 'Product published.' } )
).toBeVisible();
// Save product ID
nonVirtualProductId = page.url().match( /(?<=post=)\d+/ );
expect( nonVirtualProductId ).toBeDefined();
} );
test( 'can have a shopper add the simple non-virtual product to the cart', async ( {
page,
} ) => {
await page.goto( `/?post_type=product&p=${ nonVirtualProductId }`, {
waitUntil: 'networkidle',
} );
await expect(
page.getByRole( 'heading', { name: nonVirtualProductName } )
).toBeVisible();
await expect( page.getByText( productPrice ).first() ).toBeVisible();
await page.getByRole( 'button', { name: 'Add to cart' } ).click();
await page.getByRole( 'link', { name: 'View cart' } ).click();
await expect( page.locator( 'td[data-title=Product]' ) ).toContainText(
nonVirtualProductName
);
await expect(
page.locator( 'a.shipping-calculator-button' )
).toBeVisible();
await page
.locator( `a.remove[data-product_id='${ nonVirtualProductId }']` )
.click();
await page.waitForLoadState( 'networkidle' );
await expect(
page.locator(
`a.remove[data-product_id='${ nonVirtualProductId }']`
)
).toBeHidden();
} );
} );

View File

@ -0,0 +1,358 @@
const { test: baseTest, expect } = require( '@playwright/test' );
const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default;
baseTest.describe( 'Products > Add Simple Product', () => {
baseTest.use( { storageState: process.env.ADMINSTATE } );
const productData = {
virtual: {
name: `Virtual product ${ Date.now() }`,
description: `Virtual product longer description`,
shortDescription: `Virtual product short description`,
regularPrice: '100.05',
sku: `0_${ Date.now() }`,
virtual: true,
purchaseNote: 'Virtual product purchase note',
},
'non virtual': {
name: `Simple product ${ Date.now() }`,
description: `Simple product longer description`,
shortDescription: `Simple product short description`,
regularPrice: '100.05',
sku: `1_${ Date.now() }`,
shipping: {
weight: '2',
length: '20',
width: '10',
height: '30',
},
purchaseNote: 'Simple product purchase note',
},
downloadable: {
name: `Downloadable product ${ Date.now() }`,
regularPrice: '100.05',
description: `Downloadable product longer description`,
shortDescription: `Downloadable product short description`,
sku: `2_${ Date.now() }`,
purchaseNote: 'Downloadable product purchase note',
fileName: 'e2e-product.zip',
},
};
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 ) => {
const product = {};
await use( product );
await api.delete( `products/${ product.id }`, { force: true } );
},
category: async ( { api }, use ) => {
let category = {};
await api
.post( 'products/categories', {
name: `cat_${ Date.now() }`,
} )
.then( ( response ) => {
category = response.data;
} );
await use( category );
// Category cleanup
await api.delete( `products/categories/${ category.id }`, {
force: true,
} );
},
} );
for ( const productType of Object.keys( productData ) ) {
test( `can create a simple ${ productType } product`, async ( {
page,
category,
product,
} ) => {
await test.step( 'add new product', async () => {
await page.goto( 'wp-admin/post-new.php?post_type=product' );
} );
await test.step( 'add product name and description', async () => {
// Product name
await page
.getByLabel( 'Product name' )
.fill( productData[ productType ].name );
// Product description
await page.locator( '#content-html' ).click(); // text mode to avoid the iframe
await page
.locator( '.wp-editor-area' )
.first()
.fill( productData[ productType ].description );
await page.locator( '#excerpt-html' ).click(); // text mode to avoid the iframe
await page
.locator( '.wp-editor-area' )
.nth( 1 )
.fill( productData[ productType ].shortDescription );
} );
await test.step( 'add product price and inventory information', async () => {
// Product price
await page
.getByLabel( 'Regular price ($)' )
.fill( productData[ productType ].regularPrice );
await page.getByText( 'Inventory' ).click();
// Inventory information
await page
.getByLabel( 'SKU', { exact: true } )
.fill( productData[ productType ].sku );
} );
await test.step( 'add product attributes', async () => {
// Product attributes
const attributeName = 'attribute name';
await page
.locator( '#woocommerce-product-data' )
.getByRole( 'link', { name: 'Attributes' } )
.click();
await page
.getByPlaceholder( 'f.e. size or color' )
.fill( attributeName );
await page
.getByPlaceholder( 'Enter some descriptive text.' )
.fill( 'some attribute value' );
await page.keyboard.press( 'Enter' );
await page
.getByRole( 'button', { name: 'Save attributes' } )
.click();
await expect(
page.getByRole( 'heading', {
name: `Remove ${ attributeName }`,
} )
).toBeVisible();
} );
await test.step( 'add product advanced information', async () => {
// Advanced information
await page.getByText( 'Advanced' ).click();
await page
.getByLabel( 'Purchase note' )
.fill( productData[ productType ].purchaseNote );
await page.keyboard.press( 'Enter' );
} );
await test.step( 'add product categories', async () => {
// Using getByRole here is unreliable
const categoryCheckbox = page.locator(
`#in-product_cat-${ category.id }`
);
await categoryCheckbox.check();
await expect( categoryCheckbox ).toBeChecked();
await expect(
page
.locator( '#product_cat-all' )
.getByText( category.name )
).toBeVisible();
} );
await test.step( 'add product tags', async () => {
// Tags
await page
.getByLabel( 'Add new tag' )
.fill( 'e2e,test products' );
await page
.getByRole( 'button', { name: 'Add', exact: true } )
.click();
await expect(
page.locator( '#tagsdiv-product_tag li' ).getByText( 'e2e' )
).toBeVisible();
await expect(
page
.locator( '#tagsdiv-product_tag li' )
.getByText( 'test products' )
).toBeVisible();
} );
// eslint-disable-next-line playwright/no-conditional-in-test
if ( productData[ productType ].shipping ) {
await test.step( 'add shipping details', async () => {
await page
.getByRole( 'link', { name: 'Shipping' } )
.click();
await page
.getByPlaceholder( '0' )
.fill( productData[ productType ].shipping.weight );
await page
.getByPlaceholder( 'Length' )
.fill( productData[ productType ].shipping.length );
await page
.getByPlaceholder( 'Width' )
.fill( productData[ productType ].shipping.width );
await page
.getByPlaceholder( 'Height' )
.fill( productData[ productType ].shipping.height );
} );
}
// eslint-disable-next-line playwright/no-conditional-in-test
if ( productData[ productType ].virtual ) {
await test.step( 'add virtual product details', async () => {
await page.getByLabel( 'Virtual' ).check();
await expect( page.getByLabel( 'Virtual' ) ).toBeChecked();
} );
}
// eslint-disable-next-line playwright/no-conditional-in-test
if ( productData[ productType ].downloadable ) {
await test.step( 'add downloadable product details', async () => {
await page.getByLabel( 'Downloadable' ).check();
await expect(
page.getByLabel( 'Downloadable' )
).toBeChecked();
// Add a download link
await page.getByRole( 'link', { name: 'General' } ).click();
await page
.getByRole( 'link', { name: 'Add File' } )
.click();
await page
.getByPlaceholder( 'File name' )
.fill( productData[ productType ].fileName );
await page
.getByPlaceholder( 'http://' )
.fill(
`https://example.com/${ productData[ productType ].fileName }`
);
await page.getByPlaceholder( 'Never' ).fill( '365' );
} );
}
await test.step( 'publish the product', async () => {
await page
.getByRole( 'button', { name: 'Publish', exact: true } )
.click();
await expect(
page
.locator( 'div.notice-success > p' )
.filter( { hasText: 'Product published.' } )
).toBeVisible();
product.id = page.url().match( /(?<=post=)\d+/ )[ 0 ];
expect( product.id ).toBeDefined();
} );
await test.step( 'verify the saved product in frontend', async () => {
const permalink = await page
.locator( '#sample-permalink a' )
.innerText();
await page.goto( permalink );
// Verify product name
await expect
.soft(
page.getByRole( 'heading', {
name: productData[ productType ].name,
} )
)
.toBeVisible();
// Verify price
await expect
.soft(
page
.getByText(
productData[ productType ].regularPrice
)
.first()
)
.toBeVisible();
// Verify description
await expect
.soft(
page.getByText(
productData[ productType ].shortDescription
)
)
.toBeVisible();
await expect
.soft(
page.getByText( productData[ productType ].description )
)
.toBeVisible();
await expect
.soft(
page.getByText(
`SKU: ${ productData[ productType ].sku }`
)
)
.toBeVisible();
// Verify category
await expect
.soft(
page
.getByText( 'Category' )
.getByRole( 'link', { name: category.name } )
)
.toBeVisible();
// Verify tags
await expect
.soft(
page.getByRole( 'link', { name: 'e2e', exact: true } )
)
.toBeVisible();
await expect
.soft(
page.getByRole( 'link', {
name: 'test products',
exact: true,
} )
)
.toBeVisible();
} );
await test.step( 'shopper can add the product to cart', async () => {
// logout admin user
await page.context().clearCookies();
await page.reload();
await page
.getByRole( 'button', { name: 'Add to cart' } )
.click();
await page.getByRole( 'link', { name: 'View cart' } ).click();
await expect(
page
.getByRole( 'link' )
.filter( { hasText: productData[ productType ].name } )
).toBeVisible();
await page
.getByRole( 'link', { name: 'Proceed to checkout' } )
.click();
await expect(
page
.getByRole( 'cell' )
.filter( { hasText: productData[ productType ].name } )
).toBeVisible();
} );
} );
}
} );

View File

@ -27364,10 +27364,10 @@ index 9260fad5df..3ef0c998da 100644
+ ).toContainText( '35.99' );
} );
} );
diff --git a/plugins/woocommerce/e2e/tests/merchant/create-simple-product.spec.js b/plugins/woocommerce/e2e/tests/merchant/create-simple-product.spec.js
diff --git a/plugins/woocommerce/e2e/tests/merchant/product-create-simple.spec.js b/plugins/woocommerce/e2e/tests/merchant/product-create-simple.spec.js
index 607de7df93..58fde16c6d 100644
--- a/plugins/woocommerce/e2e/tests/merchant/create-simple-product.spec.js
+++ b/plugins/woocommerce/e2e/tests/merchant/create-simple-product.spec.js
--- a/plugins/woocommerce/e2e/tests/merchant/product-create-simple.spec.js
+++ b/plugins/woocommerce/e2e/tests/merchant/product-create-simple.spec.js
@@ -20,10 +20,13 @@ test.describe( 'Add New Simple Product Page', () => {
// and the flat rate shipping method to that zone
await api