Product Query E2E tests: Sale and Stock status filters tests (https://github.com/woocommerce/woocommerce-blocks/pull/7684)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
parent
21beb29442
commit
65afb95e35
|
@ -0,0 +1,271 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { canvas, setPostContent, insertBlock } from '@wordpress/e2e-test-utils';
|
||||
import {
|
||||
visitBlockPage,
|
||||
saveOrPublish,
|
||||
selectBlockByName,
|
||||
findToolsPanelWithTitle,
|
||||
getFixtureProductsData,
|
||||
getFormElementIdByLabel,
|
||||
shopper,
|
||||
getToggleIdByLabel,
|
||||
} from '@woocommerce/blocks-test-utils';
|
||||
import { ElementHandle } from 'puppeteer';
|
||||
import { setCheckbox, unsetCheckbox } from '@woocommerce/e2e-utils';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
GUTENBERG_EDITOR_CONTEXT,
|
||||
describeOrSkip,
|
||||
waitForCanvas,
|
||||
openBlockEditorSettings,
|
||||
} from '../../../utils';
|
||||
|
||||
const block = {
|
||||
name: 'Product Query',
|
||||
slug: 'core/query',
|
||||
class: '.wp-block-query',
|
||||
};
|
||||
|
||||
/**
|
||||
* Selectors used for interacting with the block in the editor. These selectors
|
||||
* can be changed upstream in Gutenberg, so we scope them here for
|
||||
* maintainability.
|
||||
*
|
||||
* There are also some labels that are used repeatedly, but we don't scope them
|
||||
* in favor of readability. Unlike selectors, those label are visible to end
|
||||
* users, so it's easier to understand what's going on if we don't scope them.
|
||||
* Those labels can get upated in the future, but the tests will fail and we'll
|
||||
* know to update them.
|
||||
*/
|
||||
const SELECTORS = {
|
||||
productFiltersDropdownButton: (
|
||||
{ expanded }: { expanded: boolean } = { expanded: false }
|
||||
) =>
|
||||
`.components-tools-panel-header .components-dropdown-menu button[aria-expanded="${ expanded }"]`,
|
||||
productFiltersDropdown:
|
||||
'.components-dropdown-menu__menu[aria-label="Product filters options"]',
|
||||
productFiltersDropdownItem: '.components-menu-item__button',
|
||||
editorPreview: {
|
||||
productsGrid: 'ul.wp-block-post-template',
|
||||
productsGridItem:
|
||||
'ul.wp-block-post-template > li.block-editor-block-preview__live-content',
|
||||
},
|
||||
productsGrid: `${ block.class } ul.wp-block-post-template`,
|
||||
productsGridItem: `${ block.class } ul.wp-block-post-template > li.product`,
|
||||
formTokenFieldLabel: '.components-form-token-field__label',
|
||||
tokenRemoveButton: '.components-form-token-field__remove-token',
|
||||
};
|
||||
|
||||
const toggleProductFilter = async ( filterName: string ) => {
|
||||
const $productFiltersPanel = await findToolsPanelWithTitle(
|
||||
'Product filters'
|
||||
);
|
||||
await expect( $productFiltersPanel ).toClick(
|
||||
SELECTORS.productFiltersDropdownButton()
|
||||
);
|
||||
await canvas().waitForSelector( SELECTORS.productFiltersDropdown );
|
||||
await expect( canvas() ).toClick( SELECTORS.productFiltersDropdownItem, {
|
||||
text: filterName,
|
||||
} );
|
||||
await expect( $productFiltersPanel ).toClick(
|
||||
SELECTORS.productFiltersDropdownButton( { expanded: true } )
|
||||
);
|
||||
};
|
||||
|
||||
const resetProductQueryBlockPage = async () => {
|
||||
await visitBlockPage( `${ block.name } Block` );
|
||||
await waitForCanvas();
|
||||
await setPostContent( '' );
|
||||
await insertBlock( block.name );
|
||||
await saveOrPublish();
|
||||
};
|
||||
|
||||
const getPreviewProducts = async (): Promise< ElementHandle[] > => {
|
||||
await canvas().waitForSelector( SELECTORS.editorPreview.productsGrid );
|
||||
return await canvas().$$( SELECTORS.editorPreview.productsGridItem );
|
||||
};
|
||||
|
||||
const getFrontEndProducts = async (): Promise< ElementHandle[] > => {
|
||||
await canvas().waitForSelector( SELECTORS.productsGrid );
|
||||
return await canvas().$$( SELECTORS.productsGridItem );
|
||||
};
|
||||
|
||||
describeOrSkip( GUTENBERG_EDITOR_CONTEXT === 'gutenberg' )(
|
||||
'Product Query > Products Filters',
|
||||
() => {
|
||||
let $productFiltersPanel: ElementHandle< Node >;
|
||||
beforeEach( async () => {
|
||||
/**
|
||||
* Reset the block page before each test to ensure the block is
|
||||
* inserted in a known state. This is also needed to ensure each
|
||||
* test can be run individually.
|
||||
*/
|
||||
await resetProductQueryBlockPage();
|
||||
await openBlockEditorSettings();
|
||||
await selectBlockByName( block.slug );
|
||||
$productFiltersPanel = await findToolsPanelWithTitle(
|
||||
'Product filters'
|
||||
);
|
||||
} );
|
||||
|
||||
/**
|
||||
* Reset the content of Product Query Block page after this test suite
|
||||
* to avoid breaking other tests.
|
||||
*/
|
||||
afterAll( async () => {
|
||||
await resetProductQueryBlockPage();
|
||||
} );
|
||||
|
||||
describe( 'Sale Status', () => {
|
||||
it( 'Sale status is disabled by default', async () => {
|
||||
await expect( $productFiltersPanel ).not.toMatch(
|
||||
'Show only products on sale'
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'Can add and remove Sale Status filter', async () => {
|
||||
await toggleProductFilter( 'Sale status' );
|
||||
await expect( $productFiltersPanel ).toMatch(
|
||||
'Show only products on sale'
|
||||
);
|
||||
await toggleProductFilter( 'Sale status' );
|
||||
await expect( $productFiltersPanel ).not.toMatch(
|
||||
'Show only products on sale'
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'Editor preview shows correct products corresponding to the value `Show only products on sale`', async () => {
|
||||
const defaultCount = getFixtureProductsData().length;
|
||||
const saleCount = getFixtureProductsData( 'sale_price' ).length;
|
||||
expect( await getPreviewProducts() ).toHaveLength(
|
||||
defaultCount
|
||||
);
|
||||
await toggleProductFilter( 'Sale status' );
|
||||
await setCheckbox(
|
||||
await getToggleIdByLabel( 'Show only products on sale' )
|
||||
);
|
||||
expect( await getPreviewProducts() ).toHaveLength( saleCount );
|
||||
await unsetCheckbox(
|
||||
await getToggleIdByLabel( 'Show only products on sale' )
|
||||
);
|
||||
expect( await getPreviewProducts() ).toHaveLength(
|
||||
defaultCount
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'Works on the front end', async () => {
|
||||
await toggleProductFilter( 'Sale status' );
|
||||
await setCheckbox(
|
||||
await getToggleIdByLabel( 'Show only products on sale' )
|
||||
);
|
||||
await canvas().waitForSelector(
|
||||
SELECTORS.editorPreview.productsGrid
|
||||
);
|
||||
await saveOrPublish();
|
||||
await shopper.block.goToBlockPage( block.name );
|
||||
const saleCount = getFixtureProductsData( 'sale_price' ).length;
|
||||
expect( await getFrontEndProducts() ).toHaveLength( saleCount );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'Stock Status', () => {
|
||||
it( 'Stock status is enabled by default', async () => {
|
||||
await expect( $productFiltersPanel ).toMatchElement(
|
||||
SELECTORS.formTokenFieldLabel,
|
||||
{ text: 'Stock status' }
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'Can add and remove Stock Status filter', async () => {
|
||||
await toggleProductFilter( 'Stock status' );
|
||||
await expect( $productFiltersPanel ).not.toMatchElement(
|
||||
SELECTORS.formTokenFieldLabel,
|
||||
{ text: 'Stock status' }
|
||||
);
|
||||
await toggleProductFilter( 'Stock status' );
|
||||
await expect( $productFiltersPanel ).toMatchElement(
|
||||
SELECTORS.formTokenFieldLabel,
|
||||
{ text: 'Stock status' }
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'All statuses are enabled by default', async () => {
|
||||
await expect( $productFiltersPanel ).toMatch( 'In stock' );
|
||||
await expect( $productFiltersPanel ).toMatch( 'Out of stock' );
|
||||
await expect( $productFiltersPanel ).toMatch( 'On backorder' );
|
||||
} );
|
||||
|
||||
it( 'Editor preview shows all products by default', async () => {
|
||||
const defaultCount = getFixtureProductsData().length;
|
||||
|
||||
expect( await getPreviewProducts() ).toHaveLength(
|
||||
defaultCount
|
||||
);
|
||||
} );
|
||||
|
||||
/**
|
||||
* Skipping this test for now as Product Query doesn't show correct set of products based on stock status.
|
||||
*
|
||||
* @see https://github.com/woocommerce/woocommerce-blocks/pull/7682
|
||||
*/
|
||||
it.skip( 'Editor preview shows correct products that has enabled stock statuses', async () => {
|
||||
const $$tokenRemoveButtons = await $productFiltersPanel.$$(
|
||||
SELECTORS.tokenRemoveButton
|
||||
);
|
||||
for ( const $el of $$tokenRemoveButtons ) {
|
||||
await $el.click();
|
||||
}
|
||||
|
||||
const $stockStatusInput = await canvas().$(
|
||||
await getFormElementIdByLabel(
|
||||
'Stock status',
|
||||
SELECTORS.formTokenFieldLabel.replace( '.', '' )
|
||||
)
|
||||
);
|
||||
await $stockStatusInput.click();
|
||||
await canvas().keyboard.type( 'Out of Stock' );
|
||||
await canvas().keyboard.press( 'Enter' );
|
||||
const outOfStockCount = getFixtureProductsData(
|
||||
'stock_status'
|
||||
).filter( ( status ) => status === 'outofstock' ).length;
|
||||
expect( await getPreviewProducts() ).toHaveLength(
|
||||
outOfStockCount
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'Works on the front end', async () => {
|
||||
const tokenRemoveButtons = await $productFiltersPanel.$$(
|
||||
SELECTORS.tokenRemoveButton
|
||||
);
|
||||
for ( const el of tokenRemoveButtons ) {
|
||||
await el.click();
|
||||
}
|
||||
const $stockStatusInput = await canvas().$(
|
||||
await getFormElementIdByLabel(
|
||||
'Stock status',
|
||||
SELECTORS.formTokenFieldLabel.replace( '.', '' )
|
||||
)
|
||||
);
|
||||
await $stockStatusInput.click();
|
||||
await canvas().keyboard.type( 'Out of stock' );
|
||||
await canvas().keyboard.press( 'Enter' );
|
||||
await canvas().waitForSelector(
|
||||
SELECTORS.editorPreview.productsGrid
|
||||
);
|
||||
await saveOrPublish();
|
||||
await shopper.block.goToBlockPage( block.name );
|
||||
const outOfStockCount = getFixtureProductsData(
|
||||
'stock_status'
|
||||
).filter( ( status ) => status === 'outofstock' ).length;
|
||||
expect( await getFrontEndProducts() ).toHaveLength(
|
||||
outOfStockCount
|
||||
);
|
||||
} );
|
||||
} );
|
||||
}
|
||||
);
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { canvas } from '@wordpress/e2e-test-utils';
|
||||
|
||||
export const findToolsPanelWithTitle = async ( panelTitle: string ) => {
|
||||
const panelToggleSelector = `//div[contains(@class, "components-tools-panel-header")]//h2[contains(@class, "components-heading") and contains(text(),"${ panelTitle }")]`;
|
||||
const panelSelector = `${ panelToggleSelector }/ancestor::*[contains(concat(" ", @class, " "), " components-tools-panel ")]`;
|
||||
return await canvas().waitForXPath( panelSelector );
|
||||
};
|
|
@ -6,7 +6,10 @@ import { Products } from '../e2e/fixtures/fixture-data';
|
|||
/**
|
||||
* Get products data by key from fixtures.
|
||||
*/
|
||||
export const getFixtureProductsData = ( key: string ) => {
|
||||
export const getFixtureProductsData = ( key = '' ) => {
|
||||
if ( ! key ) {
|
||||
return Products();
|
||||
}
|
||||
return Products()
|
||||
.map( ( product ) => product[ key ] )
|
||||
.filter( Boolean );
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { canvas } from '@wordpress/e2e-test-utils';
|
||||
|
||||
export const getFormElementIdByLabel = async (
|
||||
text: string,
|
||||
className: string
|
||||
) => {
|
||||
const labelElement = await canvas().waitForXPath(
|
||||
`//label[contains(text(), "${ text }") and contains(@class, "${ className }")]`,
|
||||
{ visible: true }
|
||||
);
|
||||
return await canvas().evaluate(
|
||||
( label ) => `#${ label.getAttribute( 'for' ) }`,
|
||||
labelElement
|
||||
);
|
||||
};
|
|
@ -1,7 +1,13 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { canvas } from '@wordpress/e2e-test-utils';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { DEFAULT_TIMEOUT } from './constants';
|
||||
import { getFormElementIdByLabel } from './get-form-element-id-by-label';
|
||||
|
||||
/**
|
||||
* Get the ID of the setting toogle so test can manipulate the toggle using
|
||||
|
@ -12,24 +18,20 @@ import { DEFAULT_TIMEOUT } from './constants';
|
|||
* check if the node still attached to the document before returning its
|
||||
* ID. If the node is detached, it means that the toggle is rendered, then
|
||||
* we retry by calling this function again with increased retry argument. We
|
||||
* will retry until the timeout is reached.
|
||||
* will retry until the default timeout is reached, which is 30s.
|
||||
*/
|
||||
export const getToggleIdByLabel = async (
|
||||
label: string,
|
||||
retry = 0
|
||||
): Promise< string > => {
|
||||
const delay = 1000;
|
||||
const labelElement = await page.waitForXPath(
|
||||
`//label[contains(text(), "${ label }") and contains(@class, "components-toggle-control__label")]`,
|
||||
{ visible: true }
|
||||
);
|
||||
const checkboxId = await page.evaluate(
|
||||
( toggleLabel ) => `#${ toggleLabel.getAttribute( 'for' ) }`,
|
||||
labelElement
|
||||
);
|
||||
// Wait a bit for toggle to finish rerendering.
|
||||
await page.waitForTimeout( delay );
|
||||
const checkbox = await page.$( checkboxId );
|
||||
await canvas().waitForTimeout( delay );
|
||||
const checkboxId = await getFormElementIdByLabel(
|
||||
label,
|
||||
'components-toggle-control__label'
|
||||
);
|
||||
const checkbox = await canvas().$( checkboxId );
|
||||
if ( ! checkbox ) {
|
||||
if ( retry * delay < DEFAULT_TIMEOUT ) {
|
||||
return await getToggleIdByLabel( label, retry + 1 );
|
||||
|
|
|
@ -18,6 +18,8 @@ export * from './taxes';
|
|||
export * from './constants';
|
||||
export { insertInnerBlock } from './insert-inner-block';
|
||||
export { getFixtureProductsData } from './get-fixture-products-data';
|
||||
export { findToolsPanelWithTitle } from './find-tools-panel-with-title';
|
||||
export { getFormElementIdByLabel } from './get-form-element-id-by-label';
|
||||
export { getToggleIdByLabel } from './get-toggle-id-by-label';
|
||||
export { insertBlockUsingQuickInserter } from './insert-block-using-quick-inserter';
|
||||
export { insertBlockUsingSlash } from './insert-block-using-slash';
|
||||
|
|
Loading…
Reference in New Issue