Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TestCafe wait for results #878

Merged
merged 11 commits into from
Jul 15, 2021
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions tests/acceptance/blocks/pagination.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class Pagination {
await Page.scrollToBottom(); // We must scroll to the bottom of the page to be able to click the next results page button
await VerticalResults.scrollToBottom();
await t.click(this._nextResultsButton);
await VerticalResults.waitOnSearchComplete();
}
}

Expand Down
1 change: 0 additions & 1 deletion tests/acceptance/blocks/thememap.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ class ThemeMap {

async dragLeft () {
await t.drag(this._canvas, 750, 0);
await t.wait(1000); // Wait longer than the debouncer to allow a new search to be triggered
}

async toggleSearchThisArea () {
Expand Down
42 changes: 40 additions & 2 deletions tests/acceptance/blocks/verticalresults.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Selector, t } from 'testcafe';
import { Selector, RequestLogger, ClientFunction, t } from 'testcafe';
import IE11NoCacheHook from '../ie11nocachehook';

/**
* Models the user interaction with a {import('@yext/answers-search-ui').VerticalResultsComponent}.
*/
class VerticalResults {
constructor () {
this._selector = Selector('.yxt-Results')
this._selector = Selector('.yxt-Results');
this._searchComplete = Selector('.yxt-Results--searchComplete');
this._resultsWrapper = Selector('.Answers-resultsWrapper');
this._focusedCard = Selector('.yxt-Card--pinFocused');
this._getNthCard = index => Selector(`.yxt-Card[data-opts*="${index}"]`);
Expand Down Expand Up @@ -66,6 +68,42 @@ class VerticalResults {
return this._selector.exists;
}

/**
* Wait for results to load on page by checking query response status and searchComplete state
* (timeout is set to 10 seconds)
*/
async waitOnSearchComplete() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I know I just recently approved this, but after looking at the SDK half of this item maybe this waitOnSearchComplete should go into its own request logger class instead of inside the VerticalResults component's class?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, if we can move this to it's own class we could easily expand it to universal search. Yen, if we have a new class such as SearchRequestLogger, we could have a registerVerticalSearchLogger and a registerUniversalSearchLogger function so that we could set it up for either type of search

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, yeah that sounds good

const responseWaitTimeout = 10000;
const waitTimeInterval = 200;
let totalWaitTime = 0;
while (totalWaitTime < responseWaitTimeout
&& this._queryRequestLogger.requests.find(r => !r.response || r.response.statusCode !== 200)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this code waiting while an invalid response exists? is there a reason for that approach rather than waiting until a successful response has been logged?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to extract the query logger lookup into a variable or a function so that this logic is a litter easier to follow?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logger will hold all request/response throughout a test so just checking for a successful response might accidentally check on previous requests. But, after some thoughts, I updated this method check for a successful response in the loop, and clear out the logger at the end of this method. This make sure we are always verifying the latest response.

await t.wait(waitTimeInterval);
totalWaitTime += waitTimeInterval;
}
await t.expect(this._searchComplete.exists).ok();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does await this._searchComplete() being successful imply t.expect(this._searchComplete.exists).ok() automatically?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test would still consider passed if await this._searchComplete() doesn't find any matching element

}

/**
* Register a RequestLogger that track vertical query requests to given test.
yen-tt marked this conversation as resolved.
Show resolved Hide resolved
* If the current browser is IE11, register Ie11NoCacheHook instead of default RequestLogger.
*
* @param {import('testcafe').TestController} testInstance
*/
async registerLogger(testInstance) {
const isIE11 = await ClientFunction(() => {
return !!window.MSInputMethodContext && !!document.documentMode;
})();
if (isIE11) {
this._queryRequestLogger = new IE11NoCacheHook(/v2\/accounts\/me\/answers\/vertical\/query/);
} else {
this._queryRequestLogger = RequestLogger({
url: /v2\/accounts\/me\/answers\/vertical\/query/
});
}
await testInstance.addRequestHooks(this._queryRequestLogger);
}

/**
* Gets the number of pixels that the element's content is scrolled upward
* @returns {Promise<number>}
Expand Down
31 changes: 31 additions & 0 deletions tests/acceptance/ie11nocachehook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { RequestHook } from 'testcafe';

/**
* IE11NoCacheHook solves the problem of IE11 caching all ajax requests
* by default by adding a 'no-store' header to requests in ie11.
*
* Without this hook, ajax requests cached by IE11 will be ignored
* testcafe's RequestLogger. This is only a problem in IE11.
*/
export default class IE11NoCacheHook extends RequestHook {
/**
* This comes from the suggestion here on the testcafe github
* https://github.com/DevExpress/testcafe/issues/3780#issuecomment-496955368
* The --disable-page-caching flag did not work for ie11, and neither
* did trying to set the header in onRequest(), so this workaround was used instead.
*
* @param {Object} event
*/
_onConfigureResponse (event) {
super._onConfigureResponse(event);
event.setHeader('cache-control', 'no-store');
}

async onRequest () {
// We don't need to do anything here, but still need to override the method.
}

async onResponse () {
// We don't need to do anything here, but still need to override the method.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ import CollapsibleFilters from '../blocks/collapsiblefilters';

fixture`Vertical Full Page Map with Filters and Clusters`
.page(`http://localhost:${PORT}/locations_full_page_map_with_filters`)
.beforeEach(async t => {
VerticalResults.registerLogger(t);
});

test('Clicking on a pin closes the filter view', async t => {
await SearchBar.submitQuery('virginia');
await VerticalResults.waitOnSearchComplete();
await CollapsibleFilters.viewFilters();
await ThemeMap.selectPin();
const isFilterViewOpen = await CollapsibleFilters.isFilterViewOpen();
Expand All @@ -17,6 +21,7 @@ test('Clicking on a pin closes the filter view', async t => {

test('Clicking on a cluster causes the map to zoom in', async t => {
await SearchBar.submitQuery('virginia');
await VerticalResults.waitOnSearchComplete();
const zoom = await ThemeMap.getZoom();
await ThemeMap.selectPinCluster();
const zoomAfterSelectingCluster = await ThemeMap.getZoom();
Expand All @@ -25,6 +30,7 @@ test('Clicking on a cluster causes the map to zoom in', async t => {

test('Clicking on a cluster causes a new search to be ran', async t => {
await SearchBar.submitQuery('virginia');
await VerticalResults.waitOnSearchComplete();
const numResults = await VerticalResults.getNumResults();
await ThemeMap.selectPinCluster();
const numResultsAfterSelectingCluster = await VerticalResults.getNumResults();
Expand Down
13 changes: 12 additions & 1 deletion tests/acceptance/suites/vertical-full-page-map.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,20 @@ import Pagination from '../blocks/pagination';

fixture`Vertical Full Page Map`
.page(`http://localhost:${PORT}/locations_full_page_map`)
.beforeEach(async t => {
VerticalResults.registerLogger(t);
});

test('Can search and get results', async t => {
await SearchBar.submitQuery('virginia');
await VerticalResults.waitOnSearchComplete();
const isResultsPresent = await VerticalResults.isResultsPresent();
await t.expect(isResultsPresent).ok();
});

test('Clicking on a pin focuses on a results card', async t => {
await SearchBar.submitQuery('virginia');
await VerticalResults.waitOnSearchComplete();
let isCardFocused = await VerticalResults.isCardFocused();
await t.expect(isCardFocused).notOk();
await ThemeMap.selectPin();
Expand All @@ -24,24 +29,29 @@ test('Clicking on a pin focuses on a results card', async t => {

test('Search when map moves works', async t => {
await SearchBar.submitQuery('virginia');
await VerticalResults.waitOnSearchComplete();
const resultsCountBeforeDrag = await VerticalResults.getNumResults();
await ThemeMap.dragLeft();
await ThemeMap.dragLeft();
await VerticalResults.waitOnSearchComplete();
const resultsCountAfterDrag = await VerticalResults.getNumResults();
await t.expect(resultsCountBeforeDrag !== resultsCountAfterDrag).ok();
});

test('Search this area button works', async t => {
await SearchBar.submitQuery('virginia');
await ThemeMap.toggleSearchThisArea();
await VerticalResults.waitOnSearchComplete();
const resultsCountBeforeDrag = await VerticalResults.getNumResults();
await ThemeMap.dragLeft();
await ThemeMap.clickSearchThisAreaButton();
await VerticalResults.waitOnSearchComplete();
const resultsCountAfterDrag = await VerticalResults.getNumResults();
await t.expect(resultsCountBeforeDrag !== resultsCountAfterDrag).ok();
});

test('Default initial search works and is enabled by default', async t => {
await VerticalResults.waitOnSearchComplete();
const resultsCount = await VerticalResults.getNumResults();
await t.expect(resultsCount).ok();
});
Expand All @@ -54,10 +64,11 @@ test('Pagination works', async t => {
});

test('Pagination scrolls the results to the top', async t => {
await VerticalResults.waitOnSearchComplete();
await VerticalResults.scrollToBottom();
const scrollTop = await VerticalResults.getScrollTop();
await t.expect(scrollTop).notEql(0);
await Pagination.nextResults();
const scrollTopAfterPagination = await VerticalResults.getScrollTop();
await t.expect(scrollTopAfterPagination).eql(0);
});
});