diff --git a/tests/acceptance/blocks/thememap.js b/tests/acceptance/blocks/thememap.js index 4dfba0cf5..4b20f80a1 100644 --- a/tests/acceptance/blocks/thememap.js +++ b/tests/acceptance/blocks/thememap.js @@ -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 () { diff --git a/tests/acceptance/blocks/verticalresults.js b/tests/acceptance/blocks/verticalresults.js index 2b45aebc1..74d6fd65c 100644 --- a/tests/acceptance/blocks/verticalresults.js +++ b/tests/acceptance/blocks/verticalresults.js @@ -1,11 +1,11 @@ -import { Selector, t } from 'testcafe'; +import { Selector, RequestLogger, t } from 'testcafe'; /** * 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._resultsWrapper = Selector('.Answers-resultsWrapper'); this._focusedCard = Selector('.yxt-Card--pinFocused'); this._getNthCard = index => Selector(`.yxt-Card[data-opts*="${index}"]`); diff --git a/tests/acceptance/constants.js b/tests/acceptance/constants.js index 3a85713e2..349a1d0a1 100644 --- a/tests/acceptance/constants.js +++ b/tests/acceptance/constants.js @@ -1 +1,3 @@ -module.exports.PORT=9999; \ No newline at end of file +module.exports.PORT=9999; +module.exports.VERTICAL_SEARCH_URL_REGEX=/v2\/accounts\/me\/answers\/vertical\/query/; +module.exports.UNIVERSAL_SEARCH_URL_REGEX=/v2\/accounts\/me\/answers\/query/; diff --git a/tests/acceptance/searchrequestlogger.js b/tests/acceptance/searchrequestlogger.js new file mode 100644 index 000000000..a176c8edb --- /dev/null +++ b/tests/acceptance/searchrequestlogger.js @@ -0,0 +1,62 @@ +import { VERTICAL_SEARCH_URL_REGEX, UNIVERSAL_SEARCH_URL_REGEX } from './constants'; +import { RequestLogger, t } from 'testcafe'; +import { registerIE11NoCacheHook } from '../test-utils/testcafe'; + +/** + * Handles request logger registration and request/response data received during test execution. + */ +class SearchRequestLogger { + + /** + * Register a RequestLogger that tracks vertical query requests to given test. + * If browser is IE11, register an Ie11NoCacheHook. + * + * @param {import('testcafe').TestController} testInstance + */ + async registerVerticalSearchLogger(testInstance) { + this._queryRequestLogger = RequestLogger({ + url: VERTICAL_SEARCH_URL_REGEX + }); + await testInstance.addRequestHooks(this._queryRequestLogger); + await registerIE11NoCacheHook(testInstance, VERTICAL_SEARCH_URL_REGEX); + } + + /** + * Register a RequestLogger that tracks universal query requests to given test. + * If browser is IE11, register an Ie11NoCacheHook. + * + * @param {import('testcafe').TestController} testInstance + */ + async registerUniversalSearchLogger(testInstance) { + this._queryRequestLogger = RequestLogger({ + url: UNIVERSAL_SEARCH_URL_REGEX + }); + await testInstance.addRequestHooks(this._queryRequestLogger); + await registerIE11NoCacheHook(testInstance, UNIVERSAL_SEARCH_URL_REGEX); + } + + /** + * Wait for results to load on page by checking query response status + * (timeout is set to 10 seconds) + */ + async waitOnSearchComplete() { + const responseWaitTimeout = 10000; + const waitTimeInterval = 200; + let totalWaitTime = 0; + while (totalWaitTime < responseWaitTimeout && !this.isLoggerResultsPresent()) { + await t.wait(waitTimeInterval); + totalWaitTime += waitTimeInterval; + } + this._queryRequestLogger.clear(); + } + + /** + * Returns true if there exists a query response from logger with status code 200 + * @returns {boolean} + */ + async isLoggerResultsPresent() { + return await this._queryRequestLogger.contains(r => r.response.statusCode === 200); + } +} + +export default new SearchRequestLogger(); \ No newline at end of file diff --git a/tests/acceptance/suites/vertical-full-page-map-with-filters.js b/tests/acceptance/suites/vertical-full-page-map-with-filters.js index be506efd9..49eeaa0ed 100644 --- a/tests/acceptance/suites/vertical-full-page-map-with-filters.js +++ b/tests/acceptance/suites/vertical-full-page-map-with-filters.js @@ -3,12 +3,17 @@ import SearchBar from '../blocks/searchbar'; import VerticalResults from '../blocks/verticalresults'; import ThemeMap from '../blocks/thememap'; import CollapsibleFilters from '../blocks/collapsiblefilters'; +import SearchRequestLogger from '../searchrequestlogger'; fixture`Vertical Full Page Map with Filters and Clusters` .page(`http://localhost:${PORT}/locations_full_page_map_with_filters`) + .beforeEach(async t => { + await SearchRequestLogger.registerVerticalSearchLogger(t); + }); test('Clicking on a pin closes the filter view', async t => { await SearchBar.submitQuery('virginia'); + await SearchRequestLogger.waitOnSearchComplete(); await CollapsibleFilters.viewFilters(); await ThemeMap.selectPin(); const isFilterViewOpen = await CollapsibleFilters.isFilterViewOpen(); @@ -17,6 +22,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 SearchRequestLogger.waitOnSearchComplete(); const zoom = await ThemeMap.getZoom(); await ThemeMap.selectPinCluster(); const zoomAfterSelectingCluster = await ThemeMap.getZoom(); @@ -25,6 +31,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 SearchRequestLogger.waitOnSearchComplete(); const numResults = await VerticalResults.getNumResults(); await ThemeMap.selectPinCluster(); const numResultsAfterSelectingCluster = await VerticalResults.getNumResults(); diff --git a/tests/acceptance/suites/vertical-full-page-map.js b/tests/acceptance/suites/vertical-full-page-map.js index c289accad..b0a913253 100644 --- a/tests/acceptance/suites/vertical-full-page-map.js +++ b/tests/acceptance/suites/vertical-full-page-map.js @@ -3,18 +3,24 @@ import SearchBar from '../blocks/searchbar'; import VerticalResults from '../blocks/verticalresults'; import ThemeMap from '../blocks/thememap'; import Pagination from '../blocks/pagination'; +import SearchRequestLogger from '../searchrequestlogger'; fixture`Vertical Full Page Map` .page(`http://localhost:${PORT}/locations_full_page_map`) + .beforeEach(async t => { + await SearchRequestLogger.registerVerticalSearchLogger(t); + }); test('Can search and get results', async t => { await SearchBar.submitQuery('virginia'); + await SearchRequestLogger.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 SearchRequestLogger.waitOnSearchComplete(); let isCardFocused = await VerticalResults.isCardFocused(); await t.expect(isCardFocused).notOk(); await ThemeMap.selectPin(); @@ -24,9 +30,11 @@ 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 SearchRequestLogger.waitOnSearchComplete(); const resultsCountBeforeDrag = await VerticalResults.getNumResults(); await ThemeMap.dragLeft(); await ThemeMap.dragLeft(); + await SearchRequestLogger.waitOnSearchComplete(); const resultsCountAfterDrag = await VerticalResults.getNumResults(); await t.expect(resultsCountBeforeDrag !== resultsCountAfterDrag).ok(); }); @@ -34,14 +42,17 @@ test('Search when map moves works', async t => { test('Search this area button works', async t => { await SearchBar.submitQuery('virginia'); await ThemeMap.toggleSearchThisArea(); + await SearchRequestLogger.waitOnSearchComplete(); const resultsCountBeforeDrag = await VerticalResults.getNumResults(); await ThemeMap.dragLeft(); await ThemeMap.clickSearchThisAreaButton(); + await SearchRequestLogger.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 SearchRequestLogger.waitOnSearchComplete(); const resultsCount = await VerticalResults.getNumResults(); await t.expect(resultsCount).ok(); }); @@ -49,15 +60,18 @@ test('Default initial search works and is enabled by default', async t => { test('Pagination works', async t => { const initialResultsOffset = await VerticalResults.getResultsOffset(); await Pagination.nextResults(); + await SearchRequestLogger.waitOnSearchComplete(); const updatedResultsOffset = await VerticalResults.getResultsOffset(); await t.expect(initialResultsOffset).notEql(updatedResultsOffset); }); test('Pagination scrolls the results to the top', async t => { + await SearchRequestLogger.waitOnSearchComplete(); await VerticalResults.scrollToBottom(); const scrollTop = await VerticalResults.getScrollTop(); await t.expect(scrollTop).notEql(0); await Pagination.nextResults(); + await SearchRequestLogger.waitOnSearchComplete(); const scrollTopAfterPagination = await VerticalResults.getScrollTop(); await t.expect(scrollTopAfterPagination).eql(0); -}); \ No newline at end of file +}); diff --git a/tests/test-utils/ie11nocachehook.js b/tests/test-utils/ie11nocachehook.js new file mode 100644 index 000000000..56491d9fd --- /dev/null +++ b/tests/test-utils/ie11nocachehook.js @@ -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. + } +} diff --git a/tests/test-utils/testcafe.js b/tests/test-utils/testcafe.js new file mode 100644 index 000000000..dbd298163 --- /dev/null +++ b/tests/test-utils/testcafe.js @@ -0,0 +1,18 @@ +import { ClientFunction } from 'testcafe'; +import IE11NoCacheHook from './ie11nocachehook'; + +/** + * Register the Ie11NoCacheHook, if the current browser is IE11. + * + * @param {import('testcafe').TestController} testInstance + * @param {string} url + */ + export async function registerIE11NoCacheHook (testInstance, url) { + const isIE11 = await ClientFunction(() => { + return !!window.MSInputMethodContext && !!document.documentMode; + })(); + if (isIE11) { + const ie11Hook = new IE11NoCacheHook(url); + return testInstance.addRequestHooks(ie11Hook); + } +} \ No newline at end of file