Skip to content

Commit

Permalink
Add Live search tests (#2425)
Browse files Browse the repository at this point in the history
* Add live search tests

* Add widget api methods

* Fix widger dbdata

* Fix: e2e coverage issue

* Fix pr reviews
  • Loading branch information
shashwatahalder01 authored Nov 1, 2024
1 parent 476a85d commit f31aa2c
Show file tree
Hide file tree
Showing 10 changed files with 256 additions and 53 deletions.
11 changes: 6 additions & 5 deletions tests/pw/feature-map/feature-map.yml
Original file line number Diff line number Diff line change
Expand Up @@ -775,12 +775,13 @@

- page: 'Live Search'
features:
admin:
admin can set live search options: false
admin can add live search widget: false
# admin:
# admin can set Dokan live search settings [duplicate]: true
customer:
customer can search product using live search - suggestion box: false
customer can search product using live search - autoload content: false
customer can search product using live search (suggestion box): true
customer can search product with category using live search (suggestion box): true
customer can search product using live search (autoload content): true
customer can search product with category using live search (autoload content): true

- page: 'MangoPay'
features:
Expand Down
20 changes: 18 additions & 2 deletions tests/pw/pages/basePage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,11 +299,22 @@ export class BasePage {
return response;
}

// type & wait for loadState
async typeByPageAndWaitForLoadState(selector: string, text: string, clear = true): Promise<void> {
if (clear) await this.clearInputField(selector);
await Promise.all([this.waitForLoadState(), this.page.locator(selector).pressSequentially(text, { delay: 200 })]);
}

// type & wait for response
async typeByPageAndWaitForResponse(subUrl: string, selector: string, text: string, code = 200): Promise<Response> {
async typeByPageAndWaitForResponse(subUrl: string, selector: string, text: string, code = 200, clear = true): Promise<Response> {
if (clear) await this.clearInputField(selector);
const [response] = await Promise.all([this.page.waitForResponse(resp => resp.url().includes(subUrl) && resp.status() === code), this.page.locator(selector).pressSequentially(text, { delay: 200 })]);
return response;
}
// type & wait for loadState
async typeAndWaitForLoadState(selector: string, text: string): Promise<void> {
await Promise.all([this.waitForLoadState(), this.clearAndFill(selector, text)]);
}

// type & wait for response
async typeAndWaitForResponse(subUrl: string, selector: string, text: string, code = 200): Promise<Response> {
Expand Down Expand Up @@ -683,7 +694,7 @@ export class BasePage {

// clear input field
async clearInputField(selector: string): Promise<void> {
await this.page.fill(selector, '');
await this.page.locator(selector).fill('');
}

// Or
Expand Down Expand Up @@ -823,6 +834,11 @@ export class BasePage {
return await this.page.selectOption(selector, { index: Number(value) });
}

// select by value and wait for loadState
async selectByValueAndWaitForLoadState(selector: string, value: string, state: 'load' | 'domcontentloaded' | 'networkidle' = 'domcontentloaded'): Promise<void> {
await Promise.all([this.waitForLoadState(state), this.page.selectOption(selector, { value })]);
}

// select by value and wait for response
async selectByValueAndWaitForResponse(subUrl: string, selector: string, value: string, code = 200): Promise<Response> {
const [response] = await Promise.all([this.page.waitForResponse(resp => resp.url().includes(subUrl) && resp.status() === code), this.page.selectOption(selector, { value })]);
Expand Down
30 changes: 30 additions & 0 deletions tests/pw/pages/liveSearchPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Page } from '@playwright/test';
import { BasePage } from '@pages/basePage';
import { selector } from '@pages/selectors';
import { data } from '@utils/testData';

// selectors
const liveSearchCustomer = selector.customer.cLiveSearch;

export class LiveSearch extends BasePage {
constructor(page: Page) {
super(page);
}

async searchByLiveSearch(productName: string, autoload = false, categoryName?: string) {
await this.gotoUntilNetworkidle(data.subUrls.frontend.myAccount);

if (!autoload) {
if (categoryName) await this.selectByValueAndWaitForResponse(data.subUrls.ajax, liveSearchCustomer.liveSearchCategory, categoryName.toLowerCase());
await this.fill(liveSearchCustomer.liveSearchInput, productName); // to reduce type time
await this.typeByPageAndWaitForResponse(data.subUrls.ajax, liveSearchCustomer.liveSearchInput, ' ', 200, false);
await this.toBeVisible(liveSearchCustomer.searchedResult(productName));
if (categoryName) await this.toBeVisible(liveSearchCustomer.searchResultWithCategory(productName, categoryName));
} else {
if (categoryName) await this.selectByValueAndWaitForLoadState(liveSearchCustomer.liveSearchCategory, categoryName.toLowerCase());
await this.fill(liveSearchCustomer.liveSearchInput, productName); // to reduce type time
await this.typeByPageAndWaitForLoadState(liveSearchCustomer.liveSearchInput, ' ', false);
await this.toBeVisible(selector.customer.cShop.productTitleByName(productName));
}
}
}
15 changes: 12 additions & 3 deletions tests/pw/pages/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2245,21 +2245,21 @@ export const selector = {
colorsSaveChanges: '#submit',
},

// Live Search
// live search
liveSearch: {
liveSearchOptions: '#dokan_live_search_setting\\[live_search_option\\]',
liveSearchSaveChanges: '#submit',
},

// Store Support
// store support
storeSupport: {
// Store Support
displayOnOrderDetails: '.enabled_for_customer_order .switch',
displayOnSingleProductPage: '#dokan_store_support_setting\\[store_support_product_page\\]',
supportButtonLabel: '#dokan_store_support_setting\\[support_button_label\\]',
storeSupportSaveChanges: '#submit',
},

// vendor verification
vendorVerification: {
verifiedIcon: (iconName: string) => `//label[@for='dokan_verification[verified_icon][${iconName}]']`,
verifiedIconByIcon: (iconName: string) => `//i[@class='${iconName}']//../..`,
Expand Down Expand Up @@ -6943,6 +6943,7 @@ export const selector = {
viewCart: 'a.added_to_cart',
bidNow: '.button.product_type_auction',
},
productTitleByName: (productName: string) => `//h2[@class="woocommerce-loop-product__title" and normalize-space(text())="${productName}"]`,

// Pagination
pagination: {
Expand Down Expand Up @@ -7792,6 +7793,14 @@ export const selector = {
},
},

cLiveSearch: {
liveSearchWidget: 'div.widget_dokna_product_search',
liveSearchInput: 'div.dokan-product-search input[name="s"]',
liveSearchCategory: 'div.dokan-product-search select#cat',
searchedResult: (productName: string) => `//div[@id="dokan-ajax-search-suggestion-result"]//h3[normalize-space(text())='${productName}']`,
searchResultWithCategory: (productName: string, category: string) => `//div[@id="dokan-ajax-search-suggestion-result"]//h3[normalize-space(text())='${productName}']//..//span[normalize-space(text())='${category}']`,
},

cOrderReceived: {
orderReceivedHeading: '//h1[normalize-space()="Order received"]',
orderReceivedSuccessMessage: '.woocommerce-notice.woocommerce-notice--success.woocommerce-thankyou-order-received',
Expand Down
48 changes: 48 additions & 0 deletions tests/pw/tests/e2e/liveSearch.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { test, Page, request } from '@playwright/test';
import { LiveSearch } from '@pages/liveSearchPage';
import { ApiUtils } from '@utils/apiUtils';
import { dbUtils } from '@utils/dbUtils';
import { data } from '@utils/testData';
import { dbData } from '@utils/dbData';

test.describe('Live search test', () => {
let customer: LiveSearch;
let cPage: Page;
let apiUtils: ApiUtils;

test.beforeAll(async ({ browser }) => {
const customerContext = await browser.newContext(data.auth.customerAuth);
cPage = await customerContext.newPage();
customer = new LiveSearch(cPage);

apiUtils = new ApiUtils(await request.newContext());

await dbUtils.updateOptionValue('widget_dokna_product_search', dbData.liveSearchWidget);
await dbUtils.updateOptionValue('sidebars_widgets', { ...dbData.sidebarWidgets, 'sidebar-1': ['dokna_product_search-2'] });
});

test.afterAll(async () => {
await dbUtils.setOptionValue(dbData.dokan.optionName.liveSearch, dbData.dokan.liveSearchSettings);
await cPage.close();
});

// customer

test('customer can search product using live search (suggestion box)', { tag: ['@pro', '@customer'] }, async () => {
await customer.searchByLiveSearch(data.predefined.simpleProduct.product1.name);
});

test('customer can search product with category using live search (suggestion box)', { tag: ['@pro', '@customer'] }, async () => {
await customer.searchByLiveSearch(data.predefined.simpleProduct.product1.name, false, data.predefined.categories.uncategorized);
});

test('customer can search product using live search (autoload content)', { tag: ['@pro', '@customer'] }, async () => {
await dbUtils.updateOptionValue(dbData.dokan.optionName.liveSearch, { live_search_option: 'old_live_search' });
await customer.searchByLiveSearch(data.predefined.simpleProduct.product1.name, true);
});

test('customer can search product with category using live search (autoload content)', { tag: ['@pro', '@customer'] }, async () => {
await dbUtils.updateOptionValue(dbData.dokan.optionName.liveSearch, { live_search_option: 'old_live_search' });
await customer.searchByLiveSearch(data.predefined.simpleProduct.product1.name, true, data.predefined.categories.uncategorized);
});
});
5 changes: 4 additions & 1 deletion tests/pw/tests/e2e/privacyPolicy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@ test.describe('Privacy Policy & Store Contact form test', () => {
cPage = await customerContext.newPage();
customer = new PrivacyPolicyPage(cPage);
apiUtils = new ApiUtils(await request.newContext());

// await dbUtils.updateOptionValue('widget_dokan-store-contact-widget', dbData.storeContactFormWidget);
// await dbUtils.updateOptionValue('sidebars_widgets', { ...dbData.sidebarWidgets, 'sidebar-store': ['dokan-store-contact-widget-2'] });
});

test.afterAll(async () => {
await dbUtils.updateOptionValue(dbData.dokan.optionName.privacyPolicy, { enable_privacy: 'on' });
await dbUtils.updateOptionValue(dbData.dokan.optionName.appearance, { contact_seller: 'on' });
await dbUtils.setOptionValue('sidebars_widgets', dbData.widget.emptySideBarsWidgets);
await dbUtils.setOptionValue('sidebars_widgets', dbData.emptySideBarsWidgets); // todo: remove in future
await cPage.close();
await apiUtils.dispose();
});
Expand Down
16 changes: 16 additions & 0 deletions tests/pw/utils/apiEndPoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -620,5 +620,21 @@ export const endPoints = {
createCustomPost: (postType: string) => `${SERVER_URL}/wp/v2/${postType}`,
updatePost: (postId: string) => `${SERVER_URL}/wp/v2/posts/${postId}`,
deletePost: (postId: string) => `${SERVER_URL}/wp/v2/posts/${postId}`,

// widgets
getAllWidgets: `${SERVER_URL}/wp/v2/widgets`,
getSingleWidget: (widgetId: string) => `${SERVER_URL}/wp/v2/widgets/${widgetId}`,
createWidget: `${SERVER_URL}/wp/v2/widgets`,
updateWidget: (widgetId: string) => `${SERVER_URL}/wp/v2/widgets/${widgetId}`,
deleteWidget: (widgetId: string) => `${SERVER_URL}/wp/v2/widgets/${widgetId}`,

// widget types
getAllWidgetTypes: `${SERVER_URL}/wp/v2/widget-types`,
getSingleWidgetType: (widgetId: string) => `${SERVER_URL}/wp/v2/widget-types/${widgetId}`,

// sidebars
getAllSidebars: `${SERVER_URL}/wp/v2/sidebars`,
getSingleSidebar: (sideBarId: string) => `${SERVER_URL}/wp/v2/sidebars/${sideBarId}`,
updateSidebar: (sideBarId: string) => `${SERVER_URL}/wp/v2/sidebars/${sideBarId}`,
},
};
66 changes: 66 additions & 0 deletions tests/pw/utils/apiUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1763,6 +1763,72 @@ export class ApiUtils {
return responseBody;
}

// widgets

// get all widgets
async getAllWidgets(auth?: auth): Promise<responseBody> {
const [, responseBody] = await this.get(endPoints.wp.getAllWidgets, { params: { per_page: 100 }, headers: auth });
return responseBody;
}

// get single widget
async getSingleWidget(widgetId: string, auth?: auth): Promise<responseBody> {
const [, responseBody] = await this.get(endPoints.wp.getSingleWidget(widgetId), { headers: auth });
return responseBody;
}

// create widget
async createWidget(payload: object, auth?: auth): Promise<[APIResponse, responseBody]> {
const [response, responseBody] = await this.post(endPoints.wp.createWidget, { data: payload, headers: auth });
return [response, responseBody];
}

// update widget
async updateWidget(widgetId: string, payload: object, auth?: auth): Promise<[APIResponse, responseBody]> {
const [response, responseBody] = await this.put(endPoints.wp.updateWidget(widgetId), { data: payload, headers: auth });
return [response, responseBody];
}

// delete widget
async deleteWidget(widgetId: string, payload: object, auth?: auth): Promise<[APIResponse, responseBody]> {
const [response, responseBody] = await this.delete(endPoints.wp.deleteWidget(widgetId), { data: payload, headers: auth });
return [response, responseBody];
}

// widget types

// get all widget types
async getAllWidgetTypes(auth?: auth): Promise<responseBody> {
const [, responseBody] = await this.get(endPoints.wp.getAllWidgetTypes, { params: { per_page: 100 }, headers: auth });
return responseBody;
}

// get single widget type
async getSingleWidgetTypes(widgetTypeId: string, auth?: auth): Promise<responseBody> {
const [, responseBody] = await this.get(endPoints.wp.getSingleWidgetType(widgetTypeId), { headers: auth });
return responseBody;
}

// sidebars

// get all sidebars
async getAllSidebars(auth?: auth): Promise<responseBody> {
const [, responseBody] = await this.get(endPoints.wp.getAllSidebars, { params: { per_page: 100 }, headers: auth });
return responseBody;
}

// get single sidebar
async getSingleSidebar(sidebarId: string, auth?: auth): Promise<responseBody> {
const [, responseBody] = await this.get(endPoints.wp.getSingleSidebar(sidebarId), { headers: auth });
return responseBody;
}

// update sidebar
async updateSidebar(sidebarId: string, payload: object, auth?: auth): Promise<[APIResponse, responseBody]> {
const [response, responseBody] = await this.post(endPoints.wp.updateSidebar(sidebarId), { data: payload, headers: auth });
return [response, responseBody];
}

/**
* woocommerce api methods
*/
Expand Down
Loading

0 comments on commit f31aa2c

Please sign in to comment.