diff --git a/__tests__/__image_snapshots__/chrome-docker/style-options-js-style-options-hide-scroll-to-bottom-button-1-snap.png b/__tests__/__image_snapshots__/chrome-docker/style-options-js-style-options-hide-scroll-to-bottom-button-1-snap.png new file mode 100644 index 0000000000..27efa6427c Binary files /dev/null and b/__tests__/__image_snapshots__/chrome-docker/style-options-js-style-options-hide-scroll-to-bottom-button-1-snap.png differ diff --git a/__tests__/__image_snapshots__/chrome-docker/style-options-js-style-options-hide-scroll-to-bottom-button-2-snap.png b/__tests__/__image_snapshots__/chrome-docker/style-options-js-style-options-hide-scroll-to-bottom-button-2-snap.png new file mode 100644 index 0000000000..a8e28deda9 Binary files /dev/null and b/__tests__/__image_snapshots__/chrome-docker/style-options-js-style-options-hide-scroll-to-bottom-button-2-snap.png differ diff --git a/__tests__/__image_snapshots__/chrome-docker/video-js-video-1-snap.png b/__tests__/__image_snapshots__/chrome-docker/video-js-video-1-snap.png new file mode 100644 index 0000000000..a3bf53f549 Binary files /dev/null and b/__tests__/__image_snapshots__/chrome-docker/video-js-video-1-snap.png differ diff --git a/__tests__/focus.js b/__tests__/focus.js index 7dca6c946b..1492d79404 100644 --- a/__tests__/focus.js +++ b/__tests__/focus.js @@ -86,6 +86,10 @@ describe('type focus sink', () => { await driver.wait(minNumActivitiesShown(2), timeouts.directLine); await driver.wait(scrollToBottomCompleted(), timeouts.scrollToBottom); + // For reliability reason, we are scrolling to top before focus + // This will make sure the "New messages" button show up + await pageObjects.scrollToTop(); + await driver.executeScript(() => document.querySelector('input[placeholder="Name"]').focus()); await driver .actions() diff --git a/__tests__/sendBox.js b/__tests__/sendBox.js index f58f4940e7..d8117558a8 100644 --- a/__tests__/sendBox.js +++ b/__tests__/sendBox.js @@ -1,5 +1,6 @@ import { imageSnapshotOptions, timeouts } from './constants.json'; +import actionDispatched from './setup/conditions/actionDispatched'; import minNumActivitiesShown from './setup/conditions/minNumActivitiesShown'; import uiConnected from './setup/conditions/uiConnected'; @@ -31,3 +32,23 @@ test('should focus send box when message is being sent', async () => { expect(base64PNG).toMatchImageSnapshot(imageSnapshotOptions); }); + +test('should trim outgoing message when being sent', async () => { + const { driver, pageObjects } = await setupWebDriver(); + + await driver.wait(uiConnected(), timeouts.directLine); + await pageObjects.sendMessageViaSendBox( + '\u00A0\u00A0There should be no space before and after this message.\u00A0\u00A0', + { waitForSend: false } + ); + await driver.wait( + actionDispatched( + ({ payload: { activity } = {}, type }) => + type === 'DIRECT_LINE/INCOMING_ACTIVITY' && + activity.from.role === 'user' && + activity.text === 'There should be no space before and after this message.' + ), + timeouts.directLine + ); + await driver.wait(minNumActivitiesShown(2), timeouts.directLine); +}); diff --git a/__tests__/setup/conditions/actionDispatched.js b/__tests__/setup/conditions/actionDispatched.js index e4be6f1b9f..91ec343a47 100644 --- a/__tests__/setup/conditions/actionDispatched.js +++ b/__tests__/setup/conditions/actionDispatched.js @@ -1,12 +1,32 @@ import { Condition } from 'selenium-webdriver'; -export default function actionDispatched(type) { - return new Condition( - 'Action to dispatch', - async driver => - await driver.executeScript( - type => ~window.WebChatTest.actions.findIndex(({ type: target }) => target === type), - type +export default function actionDispatched(predicateOrType) { + const message = + typeof predicateOrType === 'string' ? `action "${predicateOrType}" to dispatch` : 'action to dispatch'; + + if (typeof predicateOrType === 'string') { + const expectedType = predicateOrType; + + predicateOrType = ({ type }) => type === expectedType; + } + + return new Condition(message, async driver => { + const actions = await driver.executeScript(() => + JSON.parse( + JSON.stringify( + window.WebChatTest.actions.map(action => { + // Filter out payload on DIRECT_LINE/* because some content is not stringifiable + + if (/^DIRECT_LINE\//.test(action.type) && action.payload && action.payload.directLine) { + return simpleUpdateIn(action, ['payload', 'directLine'], () => ({})); + } + + return action; + }) + ) ) - ); + ); + + return ~actions.findIndex(action => predicateOrType(action)); + }); } diff --git a/__tests__/setup/pageObjects/index.js b/__tests__/setup/pageObjects/index.js index 4469ce8528..61f14e8a95 100644 --- a/__tests__/setup/pageObjects/index.js +++ b/__tests__/setup/pageObjects/index.js @@ -15,11 +15,13 @@ import isDictating from './isDictating'; import pingBot from './pingBot'; import playMediaToCompletion from './playMediaToCompletion'; import putSpeechRecognitionResult from './putSpeechRecognitionResult'; +import scrollToTop from './scrollToTop'; import sendFile from './sendFile'; import sendMessageViaMicrophone from './sendMessageViaMicrophone'; import sendMessageViaSendBox from './sendMessageViaSendBox'; import sendTextToClipboard from './sendTextToClipboard'; import startSpeechSynthesize from './startSpeechSynthesize'; +import switchToYouTubeIFRAME from './switchToYouTubeIFRAME'; import typeOnSendBox from './typeOnSendBox'; import updateProps from './updateProps'; @@ -51,11 +53,13 @@ export default function pageObjects(driver) { pingBot, playMediaToCompletion, putSpeechRecognitionResult, + scrollToTop, sendFile, sendMessageViaMicrophone, sendMessageViaSendBox, sendTextToClipboard, startSpeechSynthesize, + switchToYouTubeIFRAME, typeOnSendBox, updateProps }, diff --git a/__tests__/setup/pageObjects/scrollToTop.js b/__tests__/setup/pageObjects/scrollToTop.js new file mode 100644 index 0000000000..491f497803 --- /dev/null +++ b/__tests__/setup/pageObjects/scrollToTop.js @@ -0,0 +1,7 @@ +import { timeouts } from '../../constants.json'; + +export default async function scrollToTop(driver) { + await driver.executeScript(() => { + document.querySelector('[role="log"] > *').scrollTop = 0; + }, timeouts.ui); +} diff --git a/__tests__/setup/pageObjects/switchToYouTubeIFRAME.js b/__tests__/setup/pageObjects/switchToYouTubeIFRAME.js new file mode 100644 index 0000000000..b8e725c281 --- /dev/null +++ b/__tests__/setup/pageObjects/switchToYouTubeIFRAME.js @@ -0,0 +1,28 @@ +import { By, Condition, until } from 'selenium-webdriver'; +import { timeouts } from '../../constants.json'; + +function sleep(duration) { + return new Promise(resolve => setTimeout(resolve, duration)); +} + +export default async function switchToYouTubeIFRAME(driver) { + const iframeSelector = By.css('iframe[src^="https://youtube.com/"]'); + + await driver.wait(until.elementLocated(iframeSelector), timeouts.fetch); + + const iframe = await driver.findElement(iframeSelector); + + await driver.switchTo().frame(iframe); + + // TODO: [P2] Workaround the bug or bump selenium-webdriver + // selenium-webdriver has a bug that frame switching is not complete until 2 seconds later. + // There is currently no workaround other than sleeping. + await sleep(timeouts.fetch); + + await driver.wait( + new Condition('until switched to IFRAME', async driver => + /^https:\/\/www.youtube.com\//.test(await driver.executeScript(() => document.location.href)) + ), + timeouts.fetch + ); +} diff --git a/__tests__/setup/web/index.html b/__tests__/setup/web/index.html index fa1d01c04d..fa66062958 100644 --- a/__tests__/setup/web/index.html +++ b/__tests__/setup/web/index.html @@ -49,6 +49,7 @@ + diff --git a/__tests__/styleOptions.js b/__tests__/styleOptions.js index 0290f9f3d5..e2ca6abbc0 100644 --- a/__tests__/styleOptions.js +++ b/__tests__/styleOptions.js @@ -1,6 +1,7 @@ import { imageSnapshotOptions, timeouts } from './constants.json'; import minNumActivitiesShown from './setup/conditions/minNumActivitiesShown'; +import scrollToBottomCompleted from './setup/conditions/scrollToBottomCompleted'; import uiConnected from './setup/conditions/uiConnected'; // selenium-webdriver API doc: @@ -36,4 +37,21 @@ describe('style options', () => { expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions); }); + + test('hide scroll to bottom button', async () => { + const { driver, pageObjects } = await setupWebDriver(); + + await driver.wait(uiConnected(), timeouts.directLine); + await pageObjects.sendMessageViaSendBox('markdown', { waitForSend: true }); + await driver.wait(minNumActivitiesShown(2), timeouts.directLine); + await driver.wait(scrollToBottomCompleted(), timeouts.ui); + + await pageObjects.scrollToTop(); + + expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions); + + await pageObjects.updateProps({ styleOptions: { hideScrollToEndButton: true } }); + + expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions); + }); }); diff --git a/__tests__/video.js b/__tests__/video.js new file mode 100644 index 0000000000..a22afd1f30 --- /dev/null +++ b/__tests__/video.js @@ -0,0 +1,43 @@ +import { By, until } from 'selenium-webdriver'; + +import { imageSnapshotOptions, timeouts } from './constants.json'; + +import allImagesLoaded from './setup/conditions/allImagesLoaded'; +import minNumActivitiesShown from './setup/conditions/minNumActivitiesShown.js'; + +// selenium-webdriver API doc: +// https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html + +jest.setTimeout(timeouts.test); + +async function clickButton(driver, locator) { + await driver.wait(until.elementLocated(locator), timeouts.ui); + + const pauseButton = await driver.findElement(locator); + + await pauseButton.click(); +} + +test('video', async () => { + const { driver, pageObjects } = await setupWebDriver(); + + await pageObjects.sendMessageViaSendBox('video youtube', { waitForSend: true }); + + await driver.wait(allImagesLoaded(), timeouts.fetch); + await driver.wait(minNumActivitiesShown(2), timeouts.directLine); + + await pageObjects.switchToYouTubeIFRAME(); + + await clickButton(driver, By.css('button[aria-label="Play"]')); + await clickButton(driver, By.css('button[aria-label="Pause (k)"]')); + + // Hide the spinner animation + await driver.executeScript(() => document.querySelector('.ytp-spinner').remove()); + + // Wait for YouTube play/pause button animation to complete + await driver.sleep(1000); + + const base64PNG = await driver.takeScreenshot(); + + expect(base64PNG).toMatchImageSnapshot(imageSnapshotOptions); +});