Skip to content

Commit

Permalink
New test harness for HTML-based tests (#3012)
Browse files Browse the repository at this point in the history
* Initial commit

* Refinements

* Refinement on timeout

* Refinement on test output

* Refinements

* Robustness

* Ignore some non-test code

* Add code coverage

* Fix test

* Single runHTMLTest

* Rename to typeInSendBox

* Apply PR suggestions

* Prettier

* Speed up lint-staged

* Prettier

* Hide console errors when ignoring

* Test reliability
  • Loading branch information
compulim authored Apr 2, 2020
1 parent 1855c73 commit 99e0267
Show file tree
Hide file tree
Showing 116 changed files with 8,827 additions and 154 deletions.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
/__tests__/__image_snapshots__
!/packages/bundle/dist
!/packages/playground/build
!/packages/testharness/dist
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/**/nodes_modules/
/samples/**/build/static/
/samples/04.api/k.telemetry-application-insights/index.html
91 changes: 46 additions & 45 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,65 +39,66 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

### Changed

- Bumped all dependencies to latest versions, by [@compulim](https://github.com/compulim) in PR [#2985](https://github.com/microsoft/BotFramework-WebChat/pull/2985)
- Bumped all dependencies to latest versions, by [@compulim](https://github.com/compulim) in PR [#2985](https://github.com/microsoft/BotFramework-WebChat/pull/2985) and [#3012](https://github.com/microsoft/BotFramework-WebChat/pull/3012)
- Development dependencies
- Root package
- [`@azure/storage-blob@12.1.0`](https://npmjs.com/package/@azure/storage-blob)
- [`@babel/plugin-proposal-class-properties@7.8.3`](https://npmjs.com/package/@babel/plugin-proposal-class-properties)
- [`@babel/plugin-proposal-object-rest-spread@7.8.3`](https://npmjs.com/package/@babel/plugin-proposal-object-rest-spread)
- [`@babel/plugin-transform-runtime@7.8.3`](https://npmjs.com/package/@babel/plugin-transform-runtime)
- [`@babel/preset-env@7.8.7`](https://npmjs.com/package/@babel/preset-env)
- [`@babel/preset-react@7.8.3`](https://npmjs.com/package/@babel/preset-react)
- [`@babel/preset-typescript@7.8.3`](https://npmjs.com/package/@babel/preset-typescript)
- [`@babel/runtime@7.8.7`](https://npmjs.com/package/@babel/runtime)
- [`babel-jest@25.1.0`](https://npmjs.com/package/babel-jest)
- [`concurrently@5.1.0`](https://npmjs.com/package/concurrently)
- [`core-js@3.6.4`](https://npmjs.com/package/core-js)
- [`cross-env@7.0.2`](https://npmjs.com/package/cross-env)
- [`get-port@5.1.1`](https://npmjs.com/package/get-port)
- [`husky@4.2.3`](https://npmjs.com/package/husky)
- [`jest@25.1.0`](https://npmjs.com/package/jest)
- [`jest-image-snapshot@2.12.0`](https://npmjs.com/package/jest-image-snapshot)
- [`lerna@3.20.2`](https://npmjs.com/package/lerna)
- [`lint-staged@10.0.8`](https://npmjs.com/package/lint-staged)
- [`selenium-webdriver@4.0.0-alpha.7`](https://npmjs.com/package/selenium-webdriver)
- [`@azure/storage-blob@12.1.0`](https://npmjs.com/package/@azure/storage-blob)
- [`@babel/plugin-proposal-class-properties@7.8.3`](https://npmjs.com/package/@babel/plugin-proposal-class-properties)
- [`@babel/plugin-proposal-object-rest-spread@7.8.3`](https://npmjs.com/package/@babel/plugin-proposal-object-rest-spread)
- [`@babel/plugin-transform-runtime@7.8.3`](https://npmjs.com/package/@babel/plugin-transform-runtime)
- [`@babel/preset-env@7.8.7`](https://npmjs.com/package/@babel/preset-env)
- [`@babel/preset-react@7.8.3`](https://npmjs.com/package/@babel/preset-react)
- [`@babel/preset-typescript@7.8.3`](https://npmjs.com/package/@babel/preset-typescript)
- [`@babel/runtime@7.8.7`](https://npmjs.com/package/@babel/runtime)
- [`babel-jest@25.1.0`](https://npmjs.com/package/babel-jest)
- [`concurrently@5.1.0`](https://npmjs.com/package/concurrently)
- [`core-js@3.6.4`](https://npmjs.com/package/core-js)
- [`cross-env@7.0.2`](https://npmjs.com/package/cross-env)
- [`get-port@5.1.1`](https://npmjs.com/package/get-port)
- [`husky@4.2.3`](https://npmjs.com/package/husky)
- [`jest@25.1.0`](https://npmjs.com/package/jest)
- [`jest-image-snapshot@2.12.0`](https://npmjs.com/package/jest-image-snapshot)
- [`lerna@3.20.2`](https://npmjs.com/package/lerna)
- [`lint-staged@10.1.1`](https://npmjs.com/package/lint-staged)
- [`selenium-webdriver@4.0.0-alpha.7`](https://npmjs.com/package/selenium-webdriver)
- Other packages
- [`@babel/core@7.8.7`](https://npmjs.com/package/@babel/core)
- [`@babel/preset-env@7.8.7`](https://npmjs.com/package/@babel/preset-env)
- [`babel-jest@25.1.0`](https://npmjs.com/package/babel-jest)
- [`babel-plugin-istanbul@6.0.0`](https://npmjs.com/package/babel-plugin-istanbul)
- [`concurrently@5.1.0`](https://npmjs.com/package/concurrently)
- [`eslint-plugin-prettier@3.1.2`](https://npmjs.com/package/eslint-plugin-prettier)
- [`eslint-plugin-react-hooks@2.5.0`](https://npmjs.com/package/eslint-plugin-react-hooks)
- [`eslint-plugin-react@7.18.3`](https://npmjs.com/package/eslint-plugin-react)
- [`eslint@6.8.0`](https://npmjs.com/package/eslint)
- [`terser-webpack-plugin@2.3.5`](https://npmjs.com/package/terser-webpack-plugin)
- [`typescript@3.8.3`](https://npmjs.com/package/typescript)
- [`webpack-cli@3.3.11`](https://npmjs.com/package/webpack-cli)
- [`webpack-stats-plugin@0.3.1`](https://npmjs.com/package/webpack-stats-plugin)
- [`webpack@4.42.0`](https://npmjs.com/package/webpack)
- [`@babel/core@7.8.7`](https://npmjs.com/package/@babel/core)
- [`@babel/preset-env@7.8.7`](https://npmjs.com/package/@babel/preset-env)
- [`babel-jest@25.1.0`](https://npmjs.com/package/babel-jest)
- [`babel-plugin-istanbul@6.0.0`](https://npmjs.com/package/babel-plugin-istanbul)
- [`concurrently@5.1.0`](https://npmjs.com/package/concurrently)
- [`eslint-plugin-prettier@3.1.2`](https://npmjs.com/package/eslint-plugin-prettier)
- [`eslint-plugin-react-hooks@2.5.0`](https://npmjs.com/package/eslint-plugin-react-hooks)
- [`eslint-plugin-react@7.18.3`](https://npmjs.com/package/eslint-plugin-react)
- [`eslint@6.8.0`](https://npmjs.com/package/eslint)
- [`terser-webpack-plugin@2.3.5`](https://npmjs.com/package/terser-webpack-plugin)
- [`typescript@3.8.3`](https://npmjs.com/package/typescript)
- [`webpack-cli@3.3.11`](https://npmjs.com/package/webpack-cli)
- [`webpack-stats-plugin@0.3.1`](https://npmjs.com/package/webpack-stats-plugin)
- [`webpack@4.42.0`](https://npmjs.com/package/webpack)
- Production dependencies
- `core`
- [`@babel/runtime@7.8.7`](https://npmjs.com/package/@babel/runtime)
- [`redux-saga@1.1.3`](https://npmjs.com/package/redux-saga)
- [`@babel/runtime@7.8.7`](https://npmjs.com/package/@babel/runtime)
- [`redux-saga@1.1.3`](https://npmjs.com/package/redux-saga)
- `bundle`
- [`@babel/runtime@7.8.7`](https://npmjs.com/package/@babel/runtime)
- [`core-js@3.6.4`](https://npmjs.com/package/core-js)
- [`url-search-params-polyfill@8.0.0`](https://npmjs.com/package/url-search-params-polyfill)
- [`@babel/runtime@7.8.7`](https://npmjs.com/package/@babel/runtime)
- [`core-js@3.6.4`](https://npmjs.com/package/core-js)
- [`url-search-params-polyfill@8.0.0`](https://npmjs.com/package/url-search-params-polyfill)
- `component`
- [`react-redux@7.2.0`](https://npmjs.com/package/react-redux)
- [`redux@4.0.5`](https://npmjs.com/package/redux)
- [`react-redux@7.2.0`](https://npmjs.com/package/react-redux)
- [`redux@4.0.5`](https://npmjs.com/package/redux)
- `directlinespeech`
- [`@babel/runtime@7.8.7`](https://npmjs.com/package/@babel/runtime)
- [`core-js@3.6.4`](https://npmjs.com/package/core-js)
- [`@babel/runtime@7.8.7`](https://npmjs.com/package/@babel/runtime)
- [`core-js@3.6.4`](https://npmjs.com/package/core-js)
- `embed`
- [`@babel/runtime@7.8.7`](https://npmjs.com/package/@babel/runtime)
- [`core-js@3.6.4`](https://npmjs.com/package/core-js)
- [`@babel/runtime@7.8.7`](https://npmjs.com/package/@babel/runtime)
- [`core-js@3.6.4`](https://npmjs.com/package/core-js)
- Bumped Chrome Docker image to `3.141.59-zirconium` (Chrome 80.0.3987.106), by [@compulim](https://github.com/compulim) in PR [#2992](https://github.com/microsoft/BotFramework-WebChat/pull/2992)
- Added `4.8.0` to `embed/servicingPlan.json`, by [@compulim](https://github.com/compulim) in PR [#2986](https://github.com/microsoft/BotFramework-WebChat/pull/2986)
- Bumped `microsoft-cognitiveservices-speech-sdk@1.10.1` and `web-speech-cognitive-services@6.1.0`, by [@compulim](https://github.com/compulim) in PR [#3040](https://github.com/BotFramework-WebChat/pull/3040)

## Samples

- Resolves [#2806](https://github.com/microsoft/BotFramework-WebChat/issues/2806), added [Single sign-on with On Behalf Of Token Authentication](https://webchat-sample-obo.azurewebsites.net/) sample, by [@tdurnford](https://github.com/tdurnford) in [#2865](https://github.com/microsoft/BotFramework-WebChat/pull/2865)

## [4.8.0] - 2020-03-05
Expand Down
15 changes: 15 additions & 0 deletions Dockerfile-testharness2
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM node:alpine

RUN apk update && \
apk upgrade && \
apk add --no-cache bash git openssh

ENV PORT=80
EXPOSE 80
RUN npm install serve@11.3.0 -g
WORKDIR /web/
ENTRYPOINT ["npx", "--no-install", "serve", "-p", "80", "/web/__tests__/html/"]

ADD __tests__/html/ /web/__tests__/html/
ADD packages/bundle/dist/webchat-es5.js /web/packages/bundle/dist/
ADD packages/testharness/dist/testharness.js /web/packages/testharness/dist/
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
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.
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.
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.
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.
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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion __tests__/constants.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"browserName": "chrome-docker",
"imageSnapshotOptions": {
"customDiffConfig": {
"threshold": 0.14
Expand All @@ -12,7 +13,7 @@
"navigation": 10000,
"postActivity": 30000,
"scrollToBottom": 2000,
"test": 60000,
"test": 30000,
"ui": 1000
}
}
4 changes: 2 additions & 2 deletions __tests__/focus.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ test('should not focus send box after clicking on send button', async () => {

await driver.wait(uiConnected(), timeouts.directLine);

await pageObjects.typeOnSendBox('echo 123');
await pageObjects.typeInSendBox('echo 123');
await pageObjects.clickSendButton();

await driver.wait(negationOf(sendBoxTextBoxFocused()), timeouts.ui);
Expand All @@ -47,7 +47,7 @@ test('should focus send box after pressing ENTER to send message', async () => {

await driver.wait(uiConnected(), timeouts.directLine);

await pageObjects.typeOnSendBox('echo 123', Key.RETURN);
await pageObjects.typeInSendBox('echo 123', Key.RETURN);

await driver.wait(sendBoxTextBoxFocused(), timeouts.ui);
});
Expand Down
8 changes: 4 additions & 4 deletions __tests__/hooks/useActiveTyping.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ test('getter should represent bot and user typing respectively', async () => {

await expect(pageObjects.runHook('useActiveTyping')).resolves.toEqual([{}]);

await pageObjects.typeOnSendBox('typing 1');
await pageObjects.typeInSendBox('typing 1');

activeTyping = await pageObjects.runHook('useActiveTyping');

Expand All @@ -41,7 +41,7 @@ test('getter should represent bot and user typing respectively', async () => {
}
]);

await pageObjects.typeOnSendBox(Key.ENTER);
await pageObjects.typeInSendBox(Key.ENTER);
await driver.wait(minNumActivitiesShown(2), timeouts.directLine);

activeTyping = await pageObjects.runHook('useActiveTyping');
Expand All @@ -55,7 +55,7 @@ test('getter should represent bot and user typing respectively', async () => {
}
]);

await pageObjects.typeOnSendBox('.');
await pageObjects.typeInSendBox('.');

activeTyping = await pageObjects.runHook('useActiveTyping');

Expand Down Expand Up @@ -93,7 +93,7 @@ test('getter should filter out inactive typing', async () => {

await expect(pageObjects.runHook('useActiveTyping')).resolves.toEqual([{}]);

await pageObjects.typeOnSendBox('Hello, World!');
await pageObjects.typeInSendBox('Hello, World!');

activeTyping = await pageObjects.runHook('useActiveTyping');

Expand Down
2 changes: 1 addition & 1 deletion __tests__/hooks/useSendBoxValue.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ test('getter should get the send box text', async () => {

await driver.wait(uiConnected(), timeouts.directLine);

await pageObjects.typeOnSendBox('Hello, World!');
await pageObjects.typeInSendBox('Hello, World!');
await expect(pageObjects.runHook('useSendBoxValue', [], result => result[0])).resolves.toBe('Hello, World!');
});

Expand Down
2 changes: 1 addition & 1 deletion __tests__/hooks/useSubmitSendBox.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ test('calling submitSendBox should send the message in send box', async () => {

await driver.wait(uiConnected(), timeouts.directLine);

await pageObjects.typeOnSendBox('Hello, World!');
await pageObjects.typeInSendBox('Hello, World!');
await pageObjects.runHook('useSubmitSendBox', [], submitSendBox => submitSendBox());

await driver.wait(minNumActivitiesShown(2), timeouts.directLine);
Expand Down
2 changes: 1 addition & 1 deletion __tests__/hooks/useTextBox.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ test('calling submit should scroll to end', async () => {

await driver.wait(uiConnected(), timeouts.directLine);

await pageObjects.typeOnSendBox('help');
await pageObjects.typeInSendBox('help');

await expect(pageObjects.runHook('useTextBoxValue', [], textBoxValue => textBoxValue[0])).resolves.toBe('help');

Expand Down
2 changes: 1 addition & 1 deletion __tests__/hooks/useTypingIndicatorVisible.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ test('getter should return false when typing indicator is not shown', async () =
test('getter should return false when user is typing', async () => {
const { pageObjects } = await setupWebDriver({ props: { sendTypingIndicator: true } });

await pageObjects.typeOnSendBox('Hello, World!');
await pageObjects.typeInSendBox('Hello, World!');

const [typingIndicatorVisible] = await pageObjects.runHook('useTypingIndicatorVisible');

Expand Down
1 change: 1 addition & 0 deletions __tests__/html/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/__dist__
42 changes: 42 additions & 0 deletions __tests__/html/__jest__/WebChatEnvironment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const { relative } = require('path');
const AbortController = require('abort-controller');
const NodeEnvironment = require('jest-environment-node');

const { browserName } = require('../../constants.json');
const hostServe = require('./hostServe');
const serveJSON = require('../serve.json');

class WebChatEnvironment extends NodeEnvironment {
constructor(config, context) {
super(config, context);

this.abortController = new AbortController();
this.relativeTestPath = relative(process.cwd(), context.testPath);
this.global.docker = browserName === 'chrome-docker';
}

async setup() {
super.setup();

const { signal } = this.abortController;

this.global.abortSignal = signal;

if (this.global.docker) {
const { port } = await hostServe(signal, {
...serveJSON,
public: '.'
});

this.global.webServerPort = port;
}
}

async teardown() {
this.abortController.abort();

await super.teardown();
}
}

module.exports = WebChatEnvironment;
67 changes: 67 additions & 0 deletions __tests__/html/__jest__/createJobObservable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import AbortController from 'abort-controller';
import createDeferred from 'p-defer';
import Observable from 'core-js/features/observable';

import sleep from './sleep';

const ABORT_SYMBOL = Symbol();

export default function createJobObservable(driver, { ignorePageError = false } = {}) {
return new Observable(observer => {
const abortController = new AbortController();
const abortPromise = new Promise(resolve =>
abortController.signal.addEventListener('abort', () => resolve(ABORT_SYMBOL))
);

(async function() {
for (;;) {
const job = await Promise.race([driver.executeScript(() => window.WebChatTest.jobs.acquire()), abortPromise]);

if (!job) {
await sleep(50);
continue;
}

if (job === ABORT_SYMBOL) {
break;
} else if (job.type === 'done') {
observer.complete();
break;
} else if (job.type === 'error') {
observer.error(
new Error(`Unhandled exception from the test code on the page.\n\n${job.payload.error.stack}`)
);
break;
} else if (job.type === 'pageerror') {
if (!ignorePageError) {
observer.error(new Error(`The page emitted an "error" event.\n\n${job.payload.error.stack}`));
break;
}
} else {
const deferred = createDeferred();

observer.next({
deferred,
job
});

await deferred.promise.then(
(result = null) =>
driver.executeScript((id, result) => window.WebChatTest.jobs.resolve(id, result), job.id, result),
async error => {
await driver.executeScript(
(id, stack) => window.WebChatTest.jobs.reject(id, new Error(stack)),
job.id,
error.stack
);

observer.error(error);
}
);
}
}
})();

return () => abortController.abort();
});
}
22 changes: 22 additions & 0 deletions __tests__/html/__jest__/hostServe.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const { createServer } = require('http');
const getPort = require('get-port');
const serveHandler = require('serve-handler');

async function hostServe(abortSignal, serveHandlerOptions) {
return new Promise(async (resolve, reject) => {
const port = await getPort();
const httpServer = createServer((req, res) => serveHandler(req, res, serveHandlerOptions));

abortSignal &&
abortSignal.addEventListener('abort', () => {
httpServer.close();

reject(new Error('Server aborted.'));
});

httpServer.once('error', reject);
httpServer.listen(port, () => resolve({ port }));
});
}

module.exports = hostServe;
8 changes: 8 additions & 0 deletions __tests__/html/__jest__/indent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = function indent(message, count = 4) {
const indentation = new Array(count).fill(' ').join('');

return message
.split('\n')
.map(line => indentation + line)
.join('\n');
};
Loading

0 comments on commit 99e0267

Please sign in to comment.