Skip to content

Commit

Permalink
More test reliability (#1717)
Browse files Browse the repository at this point in the history
* Use jest.setTimeout instead

* Wait until postback response

* Run test suite one at a time

* Whitelist logs instead of blacklist

* Make caret transparent instead of blurring the focused element

* Typo

* Remove cursor

* Disable Docker log

* Disable Web Socket

* Correct retry count

* On retry, restart the browser

* Use concurrency of 2 on Travis CI

* Wait on driver.quit

* Use Direct Line timeout

* Re-enable Web Socket

* Revert concurrency after no improvements
  • Loading branch information
compulim committed Feb 12, 2019
1 parent e277662 commit da8aef4
Show file tree
Hide file tree
Showing 16 changed files with 73 additions and 126 deletions.
7 changes: 5 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ install:
script:
- docker-compose up --build -d
- docker-compose exec webchat ls -la
- npm test -- --coverageReporters=lcov --coverageReporters=text
- docker-compose logs
- npm test -- --coverageReporters=lcov --coverageReporters=text --maxWorkers=1

# Enable Docker log if investigation is needed
# - docker-compose logs

- docker-compose down --rmi all
- npm run coveralls

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 3 additions & 4 deletions __tests__/basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import minNumActivitiesShown from './setup/conditions/minNumActivitiesShown';
// selenium-webdriver API doc:
// https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html

jest.setTimeout(timeouts.test);

test('setup', async () => {
const { driver, pageObjects } = await setupWebDriver();

Expand All @@ -14,10 +16,7 @@ test('setup', async () => {
await driver.wait(minNumActivitiesShown(3), 2000);
await driver.wait(allImagesLoaded(), 2000);

// Hide cursor before taking screenshot
await pageObjects.hideCursor();

const base64PNG = await driver.takeScreenshot();

expect(base64PNG).toMatchImageSnapshot(imageSnapshotOptions);
}, timeouts.test);
});
62 changes: 14 additions & 48 deletions __tests__/carousel.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import minNumActivitiesShown from './setup/conditions/minNumActivitiesShown';
// selenium-webdriver API doc:
// https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html

jest.setTimeout(timeouts.test);

describe('carousel without avatar initials', () => {
test('4 attachments and no message', async () => {
const { driver, pageObjects } = await setupWebDriver();
Expand All @@ -17,9 +19,6 @@ describe('carousel without avatar initials', () => {
await driver.wait(minNumActivitiesShown(3), timeouts.directLine);
await driver.wait(allImagesLoaded(), timeouts.fetch);

// Hide cursor before taking screenshot
await pageObjects.hideCursor();

expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions);

const rightFlipper = await driver.findElement(By.css('button[aria-label="Right"]'));
Expand All @@ -33,7 +32,7 @@ describe('carousel without avatar initials', () => {
await driver.sleep(1000);

expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions);
}, timeouts.test);
});

test('4 attachments and message', async () => {
const { driver, pageObjects } = await setupWebDriver();
Expand All @@ -43,9 +42,6 @@ describe('carousel without avatar initials', () => {
await driver.wait(minNumActivitiesShown(3), timeouts.directLine);
await driver.wait(allImagesLoaded(), timeouts.fetch);

// Hide cursor before taking screenshot
await pageObjects.hideCursor();

expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions);

const rightFlipper = await driver.findElement(By.css('button[aria-label="Right"]'));
Expand All @@ -59,7 +55,7 @@ describe('carousel without avatar initials', () => {
await driver.sleep(1000);

expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions);
}, timeouts.test);
});

test('2 attachments', async () => {
const { driver, pageObjects } = await setupWebDriver();
Expand All @@ -69,11 +65,8 @@ describe('carousel without avatar initials', () => {
await driver.wait(minNumActivitiesShown(3), timeouts.directLine);
await driver.wait(allImagesLoaded(), timeouts.fetch);

// Hide cursor before taking screenshot
await pageObjects.hideCursor();

expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions);
}, timeouts.test);
});

test('2 attachments with wide screen', async () => {
const { driver, pageObjects } = await setupWebDriver({ width: 640 });
Expand All @@ -83,11 +76,8 @@ describe('carousel without avatar initials', () => {
await driver.wait(minNumActivitiesShown(3), timeouts.directLine);
await driver.wait(allImagesLoaded(), timeouts.fetch);

// Hide cursor before taking screenshot
await pageObjects.hideCursor();

expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions);
}, timeouts.test);
});

test('1 attachment', async () => {
const { driver, pageObjects } = await setupWebDriver();
Expand All @@ -97,11 +87,8 @@ describe('carousel without avatar initials', () => {
await driver.wait(minNumActivitiesShown(3), timeouts.directLine);
await driver.wait(allImagesLoaded(), timeouts.fetch);

// Hide cursor before taking screenshot
await pageObjects.hideCursor();

expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions);
}, timeouts.test);
});

test('1 attachment with wide screen', async () => {
const { driver, pageObjects } = await setupWebDriver({ width: 640 });
Expand All @@ -111,11 +98,8 @@ describe('carousel without avatar initials', () => {
await driver.wait(minNumActivitiesShown(3), timeouts.directLine);
await driver.wait(allImagesLoaded(), timeouts.fetch);

// Hide cursor before taking screenshot
await pageObjects.hideCursor();

expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions);
}, timeouts.test);
});
});

describe('carousel with avatar initials', () => {
Expand All @@ -129,9 +113,6 @@ describe('carousel with avatar initials', () => {
await driver.wait(minNumActivitiesShown(3), timeouts.directLine);
await driver.wait(allImagesLoaded(), timeouts.fetch);

// Hide cursor before taking screenshot
await pageObjects.hideCursor();

expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions);

const rightFlipper = await driver.findElement(By.css('button[aria-label="Right"]'));
Expand All @@ -145,7 +126,7 @@ describe('carousel with avatar initials', () => {
await driver.sleep(1000);

expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions);
}, timeouts.test);
});

test('4 attachments and message', async () => {
const { driver, pageObjects } = await setupWebDriver({ props: WEB_CHAT_PROPS });
Expand All @@ -155,9 +136,6 @@ describe('carousel with avatar initials', () => {
await driver.wait(minNumActivitiesShown(3), timeouts.directLine);
await driver.wait(allImagesLoaded(), timeouts.fetch);

// Hide cursor before taking screenshot
await pageObjects.hideCursor();

expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions);

const rightFlipper = await driver.findElement(By.css('button[aria-label="Right"]'));
Expand All @@ -171,7 +149,7 @@ describe('carousel with avatar initials', () => {
await driver.sleep(1000);

expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions);
}, timeouts.test);
});

test('2 attachments', async () => {
const { driver, pageObjects } = await setupWebDriver({ props: WEB_CHAT_PROPS });
Expand All @@ -181,11 +159,8 @@ describe('carousel with avatar initials', () => {
await driver.wait(minNumActivitiesShown(3), timeouts.directLine);
await driver.wait(allImagesLoaded(), timeouts.fetch);

// Hide cursor before taking screenshot
await pageObjects.hideCursor();

expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions);
}, timeouts.test);
});

test('2 attachments with wide screen', async () => {
const { driver, pageObjects } = await setupWebDriver({ props: WEB_CHAT_PROPS, width: 640 });
Expand All @@ -195,11 +170,8 @@ describe('carousel with avatar initials', () => {
await driver.wait(minNumActivitiesShown(3), timeouts.directLine);
await driver.wait(allImagesLoaded(), timeouts.fetch);

// Hide cursor before taking screenshot
await pageObjects.hideCursor();

expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions);
}, timeouts.test);
});

test('1 attachment', async () => {
const { driver, pageObjects } = await setupWebDriver({ props: WEB_CHAT_PROPS });
Expand All @@ -209,11 +181,8 @@ describe('carousel with avatar initials', () => {
await driver.wait(minNumActivitiesShown(3), timeouts.directLine);
await driver.wait(allImagesLoaded(), timeouts.fetch);

// Hide cursor before taking screenshot
await pageObjects.hideCursor();

expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions);
}, timeouts.test);
});

test('1 attachment with wide screen', async () => {
const { driver, pageObjects } = await setupWebDriver({ props: WEB_CHAT_PROPS, width: 640 });
Expand All @@ -223,9 +192,6 @@ describe('carousel with avatar initials', () => {
await driver.wait(minNumActivitiesShown(3), timeouts.directLine);
await driver.wait(allImagesLoaded(), timeouts.fetch);

// Hide cursor before taking screenshot
await pageObjects.hideCursor();

expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions);
}, timeouts.test);
});
});
10 changes: 6 additions & 4 deletions __tests__/offlineUI.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { imageSnapshotOptions, timeouts } from './constants.json';
// selenium-webdriver API doc:
// https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html

jest.setTimeout(timeouts.test);

const allOutgoingMessagesFailed = new Condition('All outgoing messages to fail sending', driver => {
return driver.executeScript(() => {
const { store } = window.WebChatTest;
Expand Down Expand Up @@ -53,7 +55,7 @@ describe('offline UI', async () => {
const base64PNG = await driver.takeScreenshot();

expect(base64PNG).toMatchImageSnapshot(imageSnapshotOptions);
}, timeouts.test);
});

test('should show "unable to connect" UI when credentials are incorrect', async () => {
const { driver } = await setupWebDriver({
Expand All @@ -80,7 +82,7 @@ describe('offline UI', async () => {
const base64PNG = await driver.takeScreenshot();

expect(base64PNG).toMatchImageSnapshot(imageSnapshotOptions);
}, timeouts.test);
});

test('should display "Send failed. Retry" when activity is not able to send', async () => {
const { driver, pageObjects } = await setupWebDriver({
Expand Down Expand Up @@ -117,7 +119,7 @@ describe('offline UI', async () => {
const base64PNG = await driver.takeScreenshot();

expect(base64PNG).toMatchImageSnapshot(imageSnapshotOptions);
}, timeouts.test);
});

test('should display "Send failed. Retry" when activity is sent but not acknowledged', async() => {
const { driver, pageObjects } = await setupWebDriver({
Expand Down Expand Up @@ -170,5 +172,5 @@ describe('offline UI', async () => {
const base64PNG = await driver.takeScreenshot();

expect(base64PNG).toMatchImageSnapshot(imageSnapshotOptions);
}, timeouts.test);
});
});
10 changes: 6 additions & 4 deletions __tests__/sendTypingIndicator.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,35 @@ import minNumActivitiesShown from './setup/conditions/minNumActivitiesShown';
// selenium-webdriver API doc:
// https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html

jest.setTimeout(timeouts.test);

test('Send typing indicator', async () => {
const { driver, pageObjects } = await setupWebDriver({ props: { sendTypingIndicator: true } });

await pageObjects.sendMessageViaSendBox('echo-typing');

await driver.wait(minNumActivitiesShown(3), 2000);
await driver.wait(minNumActivitiesShown(3), timeouts.directLine);

const input = await driver.findElement(By.css('input[type="text"]'));

await input.sendKeys('ABC');

// Typing indicator takes longer to come back
await driver.wait(minNumActivitiesShown(4), 5000);
}, timeouts.test);
});

// TODO: [P3] Take this deprecation code out when releasing on or after January 13 2020
test('Send typing indicator using deprecated props', async () => {
const { driver, pageObjects } = await setupWebDriver({ props: { sendTyping: true } });

await pageObjects.sendMessageViaSendBox('echo-typing');

await driver.wait(minNumActivitiesShown(3), 2000);
await driver.wait(minNumActivitiesShown(3), timeouts.directLine);

const input = await driver.findElement(By.css('input[type="text"]'));

await input.sendKeys('ABC');

// Typing indicator takes longer to come back
await driver.wait(minNumActivitiesShown(4), 5000);
}, timeouts.test);
});
2 changes: 1 addition & 1 deletion __tests__/setup/conditions/allOutgoingActivitiesSent.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Condition } from 'selenium-webdriver';

export default function () {
return new Condition('Waiting for Direct Line to connect', async driver => {
return new Condition('all outgoing activities to be sent', async driver => {
return await driver.executeScript(() => {
const { store } = window.WebChatTest;
const { activities } = store.getState();
Expand Down
7 changes: 0 additions & 7 deletions __tests__/setup/pageObjects/hideCursor.js

This file was deleted.

2 changes: 0 additions & 2 deletions __tests__/setup/pageObjects/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import dispatchAction from './dispatchAction';
import hideCursor from './hideCursor';
import pingBot from './pingBot';
import sendMessageViaSendBox from './sendMessageViaSendBox';

Expand All @@ -14,7 +13,6 @@ function mapMap(map, mapper) {
export default function (driver) {
return mapMap({
dispatchAction,
hideCursor,
pingBot,
sendMessageViaSendBox
}, fn => fn.bind(null, driver));
Expand Down
2 changes: 1 addition & 1 deletion __tests__/setup/retry.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export default async function (fn, retries) {
let lastErr;

for (; retries > 0; retries--) {
for (; retries >= 0; retries--) {
try {
return await fn();
} catch (err) {
Expand Down
17 changes: 11 additions & 6 deletions __tests__/setup/setupTestFramework.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,11 @@ global.setupWebDriver = async options => {
options = { ...DEFAULT_OPTIONS, ...options };

if (!driverPromise) {
driverPromise = (async () => {
driverPromise = retry(async () => {
let { baseURL, builder } = await setupTestEnvironment(BROWSER_NAME, new Builder(), options);
const driver = builder.build();
const pageObjects = createPageObjects(driver);

return await retry(async () => {
try {
// If the baseURL contains $PORT, it means it requires us to fill-in
if (/\$PORT/i.test(baseURL)) {
const { port } = await global.setupWebServer();
Expand Down Expand Up @@ -72,11 +71,17 @@ global.setupWebDriver = async options => {

await driver.wait(webChatLoaded(), timeouts.navigation);

const pageObjects = createPageObjects(driver);

options.pingBotOnLoad && await pageObjects.pingBot();

return { driver, pageObjects };
}, 3);
})();
} catch (err) {
await driver.quit();

throw err;
}
}, 3);
}

return await driverPromise;
Expand Down Expand Up @@ -123,7 +128,7 @@ afterEach(async () => {
global.__coverage__ = await driver.executeScript(() => window.__coverage__);

((await driver.executeScript(() => window.__console__)) || [])
.filter(([type]) => type !== 'info' && type !== 'log')
.filter(([type]) => type === 'error' && type === 'warn')
.forEach(([type, message]) => {
console.log(`${ type }: ${ message }`);
});
Expand Down
Loading

0 comments on commit da8aef4

Please sign in to comment.