Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Commit

Permalink
Add E2E tests for Product Collection Block (#9825)
Browse files Browse the repository at this point in the history
* Add E2E tests for Product Collection Block

This commit does the following changes:

1. Adjusts the .gitignore file to ignore the test-results and artifacts directories under all subdirectories named 'e2e-pw'.

2. Adds new E2E tests for the Product Collection block, specifically testing:
   - If the block renders correctly, including validating the number of product images, titles, prices, and 'Add to Cart' buttons.
   - If the sidebar settings correctly control the number of displayed columns.

3. Implements the 'ProductCollectionPage' class, providing methods to perform actions such as creating a new post, inserting a block, publishing a post, and locating various elements on the page.

4. Adjusts the directory structure for the E2E tests to improve organization and readability.

* Minor improvements

* Update E2E tests for Product Collection block sidebar settings

This commit updates the E2E tests for the sidebar settings of the Product Collection block. The changes include:

1. Refactoring the test assertions to use property accessors instead of method calls for the `ProductCollectionPage` class. This improves readability and consistency.

2. Updating the `ProductCollectionPage` class to initialize locators for editor and frontend elements separately. This allows easier differentiation between editor and frontend locators.

The test file `sidebar-settings.block_theme.spec.ts` has been deleted, as its functionality is now covered by the updated tests in `product-collection.block_theme.spec.ts`.

* Don't update package.json files

* Don't update gitignore file

* Add E2E test for order by control

This commit updates the E2E tests for the sidebar settings and order by control of the Product Collection block. The changes include:

1. Refactoring the test assertions and descriptions for improved readability and clarity.

2. Adding a new test case to verify the correct sorting of products by title in descending order.

3. Updating the `ProductCollectionPage` class to include a new method `setOrderBy()` to set the order by value in the order by control.

4. Adding a new method `waitForProductsToLoad()` in the `ProductCollectionPage` class to wait for the products to load in the block.

These changes ensure that the sidebar settings and order by control are functioning correctly in the Product Collection block.

* Minor improvements

* Add tests for "on sale" filter

* Add tests for Hand picked products filter

This commit updates the E2E tests for the handpicked products filter in the Product Collection block. The changes include:

1. Adding a new test case to verify that products can be filtered based on the selection in the handpicked products option.

2. Adding a new method `addFilter()` to the `ProductCollectionPage` class to select a filter option from the dropdown.

3. Adding a new method `setHandpickedProducts()` to the `ProductCollectionPage` class to set the handpicked products in the block settings.

These changes ensure that the handpicked products filter is functioning correctly in the Product Collection block.

* Verify that on sale filter works on frontend

Adding assertions to verify the count and presence of on-sale products in the frontend after publishing.

* Add tests for Keyword filter

This commit updates the E2E tests for the keyword filter in the Product Collection block. The changes include:

1. Adding assertions to verify that the products are correctly filtered based on the keyword entered.

2. Adding assertions to verify the filtered products in the frontend after publishing.

These changes ensure that the keyword filter in the Product Collection block is functioning correctly.

* Use fixture to setup product collection page

This commit refactors the E2E tests for the Product Collection block to improve test structure and readability. The changes include:

1. Refactoring the test structure using the `test.extend` function to define shared setup and teardown logic.

2. Moving the creation of the `ProductCollectionPage` instance to the shared setup logic.

3. Using the `pageObject` fixture in each test to access the `ProductCollectionPage` instance.

4. Removing duplicate code for creating the `ProductCollectionPage` instance.

These changes enhance the maintainability and readability of the E2E tests for the Product Collection block.
  • Loading branch information
imanish003 authored Jun 20, 2023
1 parent a953700 commit cb6fd92
Show file tree
Hide file tree
Showing 4 changed files with 347 additions and 2 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ tests/cli/vendor

# E2E tests
/tests/e2e-tests/config/local-*.json
/tests/e2e-pw/test-results/
/tests/e2e-pw/artifacts/
**/e2e-pw/test-results/
**/e2e-pw/artifacts/
/artifacts/

# Logs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ const OrderByControl = (
<SelectControl
value={ `${ orderBy }/${ order }` }
options={ orderOptions }
label={ __( 'Order by', 'woo-gutenberg-products-block' ) }
onChange={ ( value ) => {
const [ newOrderBy, newOrder ] = value.split( '/' );
props.setAttributes( {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/**
* External dependencies
*/
import { test as base, expect } from '@woocommerce/e2e-playwright-utils';

/**
* Internal dependencies
*/
import ProductCollectionPage from './product-collection.page';

const test = base.extend< { pageObject: ProductCollectionPage } >( {
pageObject: async ( { page, admin, editor }, use ) => {
const pageObject = new ProductCollectionPage( {
page,
admin,
editor,
} );
await pageObject.createNewPostAndInsertBlock();
await use( pageObject );
},
} );

test.describe( 'Product Collection', () => {
test( 'Renders product collection block correctly with 9 items', async ( {
pageObject,
} ) => {
expect( pageObject.productTemplate ).not.toBeNull();
expect( pageObject.productImages ).toHaveCount( 9 );
expect( pageObject.productTitles ).toHaveCount( 9 );
expect( pageObject.productPrices ).toHaveCount( 9 );
expect( pageObject.addToCartButtons ).toHaveCount( 9 );

await pageObject.publishAndGoToFrontend();

expect( pageObject.productTemplate ).not.toBeNull();
expect( pageObject.productImages ).toHaveCount( 9 );
expect( pageObject.productTitles ).toHaveCount( 9 );
expect( pageObject.productPrices ).toHaveCount( 9 );
expect( pageObject.addToCartButtons ).toHaveCount( 9 );
} );

test.describe( 'Product Collection Sidebar Settings', () => {
test( 'Reflects the correct number of columns according to sidebar settings', async ( {
pageObject,
} ) => {
await pageObject.setNumberOfColumns( 2 );
await expect(
await pageObject.productTemplate.getAttribute( 'class' )
).toContain( 'columns-2' );

await pageObject.setNumberOfColumns( 4 );
await expect(
await pageObject.productTemplate.getAttribute( 'class' )
).toContain( 'columns-4' );

await pageObject.publishAndGoToFrontend();

await expect(
await pageObject.productTemplate.getAttribute( 'class' )
).toContain( 'columns-4' );
} );

test( 'Order By - sort products by title in descending order correctly', async ( {
pageObject,
} ) => {
await pageObject.setOrderBy( 'title/desc' );
const allTitles = await pageObject.productTitles.allInnerTexts();
const expectedTitles = [ ...allTitles ].sort().reverse();

expect( allTitles ).toStrictEqual( expectedTitles );

await pageObject.publishAndGoToFrontend();

expect(
await pageObject.productTitles.allInnerTexts()
).toStrictEqual( expectedTitles );
} );

// Products can be filtered based on 'on sale' status.
test( 'Products can be filtered based on "on sale" status.', async ( {
pageObject,
} ) => {
// On each page we show 9 products.
await expect( pageObject.productImages ).toHaveCount( 9 );
// All products should not be on sale.
await expect(
await pageObject.productImages.filter( {
hasText: 'Product on sale',
} )
).not.toHaveCount( 9 );

await pageObject.setShowOnlyProductsOnSale( true );

// In test data we have only 6 products on sale
await expect( pageObject.productImages ).toHaveCount( 6 );

// Expect all shown products to be on sale.
await expect(
await pageObject.productImages.filter( {
hasText: 'Product on sale',
} )
).toHaveCount( await pageObject.productImages.count() );

await pageObject.publishAndGoToFrontend();
await expect( pageObject.productImages ).toHaveCount( 6 );
await expect(
await pageObject.productImages.filter( {
hasText: 'Product on sale',
} )
).toHaveCount( await pageObject.productImages.count() );
} );
} );

test( 'Products can be filtered based on selection in handpicked products option', async ( {
pageObject,
} ) => {
await pageObject.addFilter( 'Show Hand-picked Products' );

await pageObject.setHandpickedProducts( [ 'Album' ] );
expect( pageObject.productTitles ).toHaveCount( 1 );

const productNames = [ 'Album', 'Cap' ];
await pageObject.setHandpickedProducts( productNames );
expect( pageObject.productTitles ).toHaveCount( 2 );
expect( pageObject.productTitles ).toHaveText( productNames );

await pageObject.publishAndGoToFrontend();
expect( pageObject.productTitles ).toHaveCount( 2 );
expect( pageObject.productTitles ).toHaveText( productNames );
} );

test( 'Products can be filtered based on keyword.', async ( {
page,
admin,
editor,
} ) => {
const pageObject = new ProductCollectionPage( {
page,
admin,
editor,
} );
await pageObject.createNewPostAndInsertBlock();
await pageObject.addFilter( 'Keyword' );

await pageObject.setKeyword( 'Album' );
expect( pageObject.productTitles ).toHaveText( [ 'Album' ] );

await pageObject.setKeyword( 'Cap' );
expect( pageObject.productTitles ).toHaveText( [ 'Cap' ] );

await pageObject.publishAndGoToFrontend();
expect( pageObject.productTitles ).toHaveText( [ 'Cap' ] );
} );
} );
190 changes: 190 additions & 0 deletions tests/e2e-pw/tests/product-collection/product-collection.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/**
* External dependencies
*/
import { Locator, Page } from '@playwright/test';
import { Editor, Admin } from '@wordpress/e2e-test-utils-playwright';

class ProductCollectionPage {
private page: Page;
private admin: Admin;
private editor: Editor;
productTemplate!: Locator;
productImages!: Locator;
productTitles!: Locator;
productPrices!: Locator;
addToCartButtons!: Locator;

constructor( {
page,
admin,
editor,
}: {
page: Page;
admin: Admin;
editor: Editor;
} ) {
this.page = page;
this.admin = admin;
this.editor = editor;
}

async createNewPostAndInsertBlock() {
await this.admin.createNewPost();
await this.editor.insertBlock( {
name: 'woocommerce/product-collection',
} );
await this.refreshLocators( 'editor' );
}

async publishAndGoToFrontend() {
await this.editor.publishPost();
const url = new URL( this.page.url() );
const postId = url.searchParams.get( 'post' );
await this.page.goto( `/?p=${ postId }`, { waitUntil: 'networkidle' } );
await this.refreshLocators( 'frontend' );
}

async addFilter( name: 'Show Hand-picked Products' | 'Keyword' ) {
await this.page
.getByRole( 'button', { name: 'Filters options' } )
.click();
await this.page
.getByRole( 'menuitemcheckbox', {
name,
} )
.click();
await this.page
.getByRole( 'button', { name: 'Filters options' } )
.click();
}

async setNumberOfColumns( numberOfColumns: number ) {
const inputField = await this.page.getByRole( 'spinbutton', {
name: 'Columns',
} );
await inputField.fill( numberOfColumns.toString() );
}

async setOrderBy(
orderBy:
| 'title/asc'
| 'title/desc'
| 'date/desc'
| 'date/asc'
| 'popularity/desc'
| 'rating/desc'
) {
const orderByComboBox = await this.page.getByRole( 'combobox', {
name: 'Order by',
} );
await orderByComboBox.selectOption( orderBy );
await this.refreshLocators( 'editor' );
}

async setShowOnlyProductsOnSale( onSale: boolean ) {
const input = this.page.getByLabel( 'Show only products on sale' );
if ( onSale ) {
await input.check();
} else {
await input.uncheck();
}
await this.refreshLocators( 'editor' );
}

async setHandpickedProducts( names: string[] ) {
const input = this.page.getByLabel( 'Pick some products' );
await input.click();

// Clear the input field.
let numberOfAlreadySelectedProducts = await input.evaluate(
( node ) => {
return node.parentElement?.children.length;
}
);
while ( numberOfAlreadySelectedProducts ) {
// Backspace will remove token
await this.page.keyboard.press( 'Backspace' );
numberOfAlreadySelectedProducts--;
}

// Add new products.
for ( const name of names ) {
await input.fill( name );
await this.page
.getByRole( 'option', { name } )
.getByText( name )
.click();
}

await this.refreshLocators( 'editor' );
}

async setKeyword( keyword: string ) {
const input = this.page.getByLabel( 'Keyword' );
await input.clear();
await input.fill( keyword );
// Timeout is needed because of debounce in the block.
await this.page.waitForTimeout( 300 );
await this.refreshLocators( 'editor' );
}

/**
* Private methods to be used by the class.
*/
private async refreshLocators( currentUI: 'editor' | 'frontend' ) {
await this.waitForProductsToLoad();

if ( currentUI === 'editor' ) {
await this.initializeLocatorsForEditor();
} else {
await this.initializeLocatorsForFrontend();
}
}

private async initializeLocatorsForEditor() {
this.productTemplate = await this.page.locator(
'.wc-block-product-template'
);
this.productImages = await this.page
.locator( '[data-type="woocommerce/product-image"]' )
.locator( 'visible=true' );
this.productTitles = await this.productTemplate
.locator( '.wp-block-post-title' )
.locator( 'visible=true' );
this.productPrices = await this.page
.locator( '[data-type="woocommerce/product-price"]' )
.locator( 'visible=true' );
this.addToCartButtons = await this.page
.locator( '[data-type="woocommerce/product-button"]' )
.locator( 'visible=true' );
}

private async initializeLocatorsForFrontend() {
this.productTemplate = await this.page.locator(
'.wc-block-product-template'
);
this.productImages = await this.productTemplate.locator(
'[data-block-name="woocommerce/product-image"]'
);
this.productTitles = await this.productTemplate.locator(
'.wp-block-post-title'
);
this.productPrices = await this.productTemplate.locator(
'[data-block-name="woocommerce/product-price"]'
);
this.addToCartButtons = await this.productTemplate.locator(
'[data-block-name="woocommerce/product-button"]'
);
}

private async waitForProductsToLoad() {
await this.page.waitForLoadState( 'networkidle' );
// Wait for the product blocks to be loaded.
await this.page.waitForSelector( '.wc-block-product' );
// Wait for the loading spinner to be detached.
await this.page.waitForSelector( '.is-loading', { state: 'detached' } );
await this.page.waitForLoadState( 'networkidle' );
}
}

export default ProductCollectionPage;

0 comments on commit cb6fd92

Please sign in to comment.