Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add visual regression test and code coverage report #1323

Merged
merged 23 commits into from
Nov 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"plugins": [
"@babel/plugin-proposal-object-rest-spread"
],
"presets": [
["@babel/preset-env", {
"forceAllTransforms": true,
"modules": "commonjs"
}],
"@babel/preset-react"
]
}
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
/__tests__/__image_snapshots__/**/__diff_output__
/coverage
/debug.log
/gh-pages
/lerna-debug.log
/node_modules

# Do not commit binaries
/chromedriver*
9 changes: 7 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,15 @@ install:
- npm run bootstrap
- npm run build
- npm run prepublishOnly
- docker build -t webchat.azurecr.io/playground .
- docker build -f Dockerfile-playground -t webchat.azurecr.io/playground .

script:
- echo
- docker-compose up --build -d
- docker-compose exec webchat ls -la
- npm test -- --coverageReporters=lcov --coverageReporters=text
- docker-compose logs
- docker-compose down --rmi all
- npm run coveralls

before_deploy:
- git config --local user.name "Bot Framework"
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- Development build now include instrumentation code, updated build scripts
- `npm run build` will build for development with instrumentation code
- `npm run prepublishOnly` will build for production
- `npm run watch` will also run Webpack in watch loop
- Automated testing using visual regression testing technique
- [Docker-based](https://github.com/SeleniumHQ/docker-selenium) automated testing using headless Chrome and [Web Driver](https://npmjs.com/packages/selenium-webdriver)
- Screenshot comparison using [`jest-image-snapshot`](https://npmjs.com/packages/jest-image-snapshot) and [`pixelmatch`](https://npmjs.com/package/pixelmatch)
- Code is instrumented using [`istanbul`](https://npmjs.com/package/istanbul)
- Test report is hosted on [Coveralls](https://coveralls.io/github/compulim/BotFramework-WebChat)

### Changed
- Bump dependencies, in [#1303](https://github.com/Microsoft/BotFramework-WebChat/pull/1303)
- `@babel`
Expand Down
File renamed without changes.
14 changes: 14 additions & 0 deletions Dockerfile-testharness
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM node:alpine

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

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

ADD __tests__/setup/web/ /web
ADD packages/bundle/dist /web
WORKDIR /web
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<p align="center">
<a href="https://badge.fury.io/js/botframework-webchat"><img alt="npm version" src="https://badge.fury.io/js/botframework-webchat.svg" /></a>
<a href="https://travis-ci.org/Microsoft/BotFramework-WebChat"><img alt="Build Status" src="https://travis-ci.org/Microsoft/BotFramework-WebChat.svg?branch=master" /></a>
<a href="https://coveralls.io/github/Microsoft/BotFramework-WebChat?branch=master"><img src="https://coveralls.io/repos/github/Microsoft/BotFramework-WebChat/badge.svg?branch=master" alt="Coverage Status" /></a>
</p>

# How to use
Expand Down Expand Up @@ -151,7 +152,15 @@ npm run bootstrap
npm run build
```

> Instead of `npm run build`, you can use `npm run watch` for watch mode.
## Build tasks

There are 3 types of build tasks in the build process.

- `npm run build`: Build for development (instrumented code for code coverage)
- Will bundle as `webchat-instrumented*.js`
- `npm run watch`: Build for development with watch mode loop
- `npm run prepublishOnly`: Build for production
- Will bundle as `webchat*.js`

## Testing Web Chat for development purpose

Expand Down
27 changes: 27 additions & 0 deletions __tests__/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## How to run tests

Automated testing in Web Chat is using multiple open-source technologies.

- [Travis CI](https://travis-ci.org/) for automatic testing
- Test against [MockBot](https://github.com/compulim/BotFramework-MockBot)
- Try it out with this [live demo](https://microsoft.github.io/BotFramework-WebChat/full-bundle)
- Visual regression test (a.k.a. compare screenshots)
- Generated on [Chrome on Docker](https://github.com/SeleniumHQ/docker-selenium)
- Compared using [`pixelmatch`](https://npmjs.com/package/pixelmatch) via [`jest-image-snapshot`](https://npmjs.com/package/jest-image-snapshot)
- Run under [`Jest`](https://jestjs.io/)
- [`Istanbul`](https://npmjs.com/package/istanbul) for code coverage
- [`Coveralls`](https://coveralls.io/) for test statistics

### Running tests under Docker

- Install Docker
- On Windows, set environment variable `COMPOSE_CONVERT_WINDOWS_PATHS=1`
- `docker-compose up --build`
- `npm test`

### Running tests under local box

- Install latest Chrome
- Download [ChromeDriver](https://sites.google.com/a/chromium.org/chromedriver/downloads) and extract to project root
- Set environment variable `WEBCHAT_TEST_ENV=chrome-local`
- `npm test`
2 changes: 2 additions & 0 deletions __tests__/__image_snapshots__/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Ignore baseline images from local build because they "Works on my machine <TM>"
/chrome-local
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions __tests__/basic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { By, Key } from 'selenium-webdriver';

function sleep(ms = 1000) {
return new Promise(resolve => setTimeout(resolve, ms));
}

// selenium-webdriver API doc:
// https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html

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

await sleep(2000);

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

await input.sendKeys('layout carousel', Key.RETURN);
await sleep(2000);

const base64PNG = await driver.takeScreenshot();

expect(base64PNG).toMatchImageSnapshot();
}, 60000);
25 changes: 25 additions & 0 deletions __tests__/setup/setupTestEnvironment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Options } from 'selenium-webdriver/chrome';

export default function (browserName, builder) {
switch (browserName) {
case 'chrome-local':
return {
baseURL: 'http://localhost:$PORT/index.html',
builder: builder.forBrowser('chrome').setChromeOptions(
(builder.getChromeOptions() || new Options())
.windowSize({ height: 640, width: 360 })
)
};

case 'chrome-docker':
default:
return {
baseURL: 'http://webchat/',
builder: builder.forBrowser('chrome').usingServer('http://localhost:4444/wd/hub').setChromeOptions(
(builder.getChromeOptions() || new Options())
.headless()
.windowSize({ height: 640, width: 360 })
)
};
}
};
100 changes: 100 additions & 0 deletions __tests__/setup/setupTestFramework.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { Builder } from 'selenium-webdriver';
import { createServer } from 'http';
import { join } from 'path';
import { promisify } from 'util';
import { configureToMatchImageSnapshot } from 'jest-image-snapshot';
import getPort from 'get-port';
import handler from 'serve-handler';

import setupTestEnvironment from './setupTestEnvironment';

const BROWSER_NAME = process.env.WEBCHAT_TEST_ENV || 'chrome-docker';
// const BROWSER_NAME = 'chrome-docker';
// const BROWSER_NAME = 'chrome-local';

expect.extend({
toMatchImageSnapshot: configureToMatchImageSnapshot({
customSnapshotsDir: join(__dirname, '../__image_snapshots__', BROWSER_NAME)
})
});

let driverPromise;
let serverPromise;

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

// If the baseURL contains $PORT, it means it requires us to fill-in
if (/\$PORT/i.test(baseURL)) {
const { port } = await global.setupWebServer();

await driver.get(baseURL.replace(/\$PORT/ig, port));
} else {
await driver.get(baseURL);
}

return { driver };
})();
}

return await driverPromise;
};

global.setupWebServer = async () => {
if (!serverPromise) {
serverPromise = new Promise(async (resolve, reject) => {
const port = await getPort();
const httpServer = createServer((req, res) => handler(req, res, {
redirects: [
{ source: '/', destination: '__tests__/setup/web/index.html' }
],
rewrites: [
{ source: '/webchat.js', destination: 'packages/bundle/dist/webchat.js' },
{ source: '/webchat-es5.js', destination: 'packages/bundle/dist/webchat-es5.js' },
{ source: '/webchat-minimal.js', destination: 'packages/bundle/dist/webchat-minimal.js' }
],
public: join(__dirname, '../..'),
}));

httpServer.once('error', reject);

httpServer.listen(port, () => {
resolve({
close: promisify(httpServer.close.bind(httpServer)),
port
});
});
});
}

return await serverPromise;
}

afterEach(async () => {
if (driverPromise) {
const { driver } = await driverPromise;

try {
global.__coverage__ = await driver.executeScript(() => window.__coverage__);

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

afterAll(async () => {
if (serverPromise) {
const { close } = await serverPromise;

await close();
}
});
75 changes: 75 additions & 0 deletions __tests__/setup/web/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<title>Web Chat: Automated test harness</title>
<script>
!function () {
'use strict';

const { console } = window;
const log = window.__console__ = [];
const push = (type, ...args) => {
log.push([type, args.join(' ')]);
console[type].apply(console, args);
};

window.console = {
error: push.bind(null, 'error'),
info: push.bind(null, 'info'),
log: push.bind(null, 'log'),
warn: push.bind(null, 'warn')
};

window.addEventListener('error', ({ colno, error, filename, lineno, message }) => {
push('onError', JSON.stringify({
colno,
error,
filename,
lineno,
message
}));
});
}();
</script>
<!--
For demonstration purpose, we are using development branch of Web Chat at "/master/webchat.js".
When you are using Web Chat for production, you should use the latest stable at "/latest/webchat.js".
Or locked down on a specific version "/4.1.0/webchat.js".
-->
<script src="/webchat-instrumented.js"></script>
<style>
html, body { height: 100% }
body { margin: 0 }

#webchat,
#webchat > * {
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<div id="webchat"></div>
<script>
(async function () {
// In this demo, we are using Direct Line token from MockBot.
// To talk to your bot, you should use the token exchanged using your Direct Line secret.
// You should never put the Direct Line secret in the browser or client app.
// https://docs.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-direct-line-3-0-authentication

const res = await fetch('https://webchat-mockbot.azurewebsites.net/directline/token', { method: 'POST' });
const { token } = await res.json();

window.WebChat.renderWebChat({
// directLine: window.WebChat.createDirectLine({
// domain: 'http://localhost:5000/v3/directline',
// webSocket: false
// })
directLine: window.WebChat.createDirectLine({ token })
}, document.getElementById('webchat'));

document.querySelector('#webchat > *').focus();
})().catch(err => console.error(err));
</script>
</body>
</html>
28 changes: 28 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
version: '3'

services:
# On Windows, run with COMPOSE_CONVERT_WINDOWS_PATHS=1

chrome:
# https://github.com/SeleniumHQ/docker-selenium
# https://hub.docker.com/r/selenium/standalone-chrome/tags/
image: selenium/standalone-chrome:3.141.0-actinium
networks:
- selenium
depends_on:
- webchat
ports:
- "4444:4444"
volumes:
- /dev/shm:/dev/shm

webchat:
build:
context: ./
dockerfile: Dockerfile-testharness
networks:
- selenium

networks:
selenium:
driver: bridge
Loading