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);
+});