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

Add E2E tests for Product Collection Block #9825

Merged
merged 16 commits into from
Jun 20, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,185 @@
/**
* External dependencies
*/
import { test, expect } from '@woocommerce/e2e-playwright-utils';

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

test.describe( 'Product Collection', () => {
test( 'Renders product collection block correctly with 9 items', async ( {
page,
admin,
editor,
} ) => {
const pageObject = new ProductCollectionPage( {
imanish003 marked this conversation as resolved.
Show resolved Hide resolved
page,
admin,
editor,
} );
await pageObject.createNewPostAndInsertBlock();

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 ( {
page,
admin,
editor,
} ) => {
const pageObject = new ProductCollectionPage( {
page,
admin,
editor,
} );
await pageObject.createNewPostAndInsertBlock();
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 ( {
page,
admin,
editor,
} ) => {
const pageObject = new ProductCollectionPage( {
page,
admin,
editor,
} );
await pageObject.createNewPostAndInsertBlock();

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 ( {
page,
admin,
editor,
} ) => {
const pageObject = new ProductCollectionPage( {
page,
admin,
editor,
} );
await pageObject.createNewPostAndInsertBlock();

// 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 ( {
page,
admin,
editor,
} ) => {
const pageObject = new ProductCollectionPage( {
page,
admin,
editor,
} );
await pageObject.createNewPostAndInsertBlock();
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;