Skip to content

Commit

Permalink
New: Setup selenium functional UI framework (#158)
Browse files Browse the repository at this point in the history
* Set up express app to generate annotator tokens and test browsers
* Update travis.yml w/ saucelabs config
* Allowing concurrent testing w/ file ids in env variables
* Chore: Only run tests on cron jobs
* Chore: Only run enabled functional-tests
* Chore: Run tests using all files in functional-tests/tests/ folder
* Chore: Send travis alert emails to [email protected]
  • Loading branch information
pramodsum authored Apr 12, 2018
1 parent 7f62860 commit db9a320
Show file tree
Hide file tree
Showing 16 changed files with 3,200 additions and 1,243 deletions.
2 changes: 1 addition & 1 deletion .babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"**/*-test.js",
"build/**",
"docs/**",
"lib'**",
"lib'**"
]
}],
["babel-plugin-transform-require-ignore", { "extensions": [".scss"] }]
Expand Down
6 changes: 5 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@
"assert": false,
"fixture": false,
"__": false,
"Assert": false
"Assert": false,
"Feature": false,
"Before": false,
"BeforeSuite": false,
"Scenario": false
},
"rules": {
"quotes": [
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ src/i18n/json
test/dev.html
lib
*~~bak
functional-tests/output
62 changes: 53 additions & 9 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,57 @@
# @desktop @mobile @enabled
language: node_js
node_js:
- "8"
script: yarn run ci
- '8'
cache: yarn
notifications:
email: false

# safelist
branches:
only:
- master
- /^greenkeeper/.*$/
email:
recipients:
- [email protected]
aliases:
- &sauce-labs
if: type = cron
before_script:
- yarn run setup-travis &
- sleep 5
addons:
sauce_connect: true
script: yarn run functional-tests-ci
- &sauce-labs-mobile
<<: *sauce-labs
script: yarn run functional-tests-ci --grep @mobile --grep @enabled
- &sauce-labs-desktop
<<: *sauce-labs
script: yarn run functional-tests-ci --grep @desktop --grep @enabled
jobs:
include:
- script: yarn run ci
# Mac Chrome
- <<: *sauce-labs-desktop
env: BROWSER_PLATFORM="macOS 10.13" BROWSER_NAME="chrome" FILE_ID="285567874839" FILE_VERSION_ID="300496591287"
# Mac Safari
- <<: *sauce-labs-desktop
env: BROWSER_PLATFORM="macOS 10.13" BROWSER_NAME="safari" FILE_ID="285569765346" FILE_VERSION_ID="300498497346"
script: yarn run functional-tests-ci
# Mac Firefox
- <<: *sauce-labs-desktop
env: BROWSER_PLATFORM="macOS 10.13" BROWSER_NAME="firefox" FILE_ID="285568802145" FILE_VERSION_ID="300497533713"
# Windows Edge
- <<: *sauce-labs-desktop
env: BROWSER_PLATFORM="Windows 10" BROWSER_NAME="MicrosoftEdge" FILE_ID="285567976309" FILE_VERSION_ID="300496707445"
script: yarn run functional-tests-ci
# Windows IE
- <<: *sauce-labs-desktop
env: BROWSER_PLATFORM="Windows 10" BROWSER_NAME="internet explorer" FILE_ID="285568624824" FILE_VERSION_ID="300497342136"
script: yarn run functional-tests-ci
# iPhone
- <<: *sauce-labs-mobile
env: BROWSER_PLATFORM="iOS" DEVICE_NAME="iPhone 6 Simulator" PLATFORM_VERSION="11.2" BROWSER_NAME="Safari" FILE_ID="285569765346" FILE_VERSION_ID="300498497346"
# iPad
- <<: *sauce-labs-mobile
# Uses firefox file id
env: BROWSER_PLATFORM="iOS" DEVICE_NAME="iPad Simulator" PLATFORM_VERSION="11.2" BROWSER_NAME="Safari" FILE_ID="285568802145" FILE_VERSION_ID="300497533713"
# Android
- <<: *sauce-labs-mobile
# Uses chrome file id
env: BROWSER_PLATFORM="Android" DEVICE_NAME="Android GoogleAPI Emulator" PLATFORM_VERSION="7.1" BROWSER_NAME="Chrome" FILE_ID="285567874839" FILE_VERSION_ID="300496591287"
script: yarn run functional-tests-ci
27 changes: 27 additions & 0 deletions build/webpack.selenium.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
require('babel-polyfill');

const isRelease = process.env.NODE_ENV === 'production';
const isDev = process.env.NODE_ENV === 'dev';

const path = require('path');
const commonConfig = require('./webpack.common.config');
const { UglifyJsPlugin } = require('webpack').optimize;
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const { BannerPlugin } = require('webpack');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const license = require('./license');

/* eslint-disable key-spacing, require-jsdoc */
const config = Object.assign(commonConfig(), {
entry: {
annotations: ['./src/BoxAnnotations.js']
},
output: {
path: path.resolve('functional-tests/lib'),
filename: '[Name].js'
}
});

config.devtool = 'inline-source-map';

module.exports = config;
75 changes: 75 additions & 0 deletions codecept.conf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
const {
SAUCE_USERNAME,
SAUCE_ACCESS_KEY,
TRAVIS_JOB_NUMBER,
CI,
BROWSER_NAME = 'chrome',
BROWSER_VERSION = 'latest',
BROWSER_PLATFORM,
PLATFORM_VERSION,
DEVICE_NAME,
DEFAULT_WAIT_TIME = 9000
} = process.env;
const MOBILE_PLATFORMS = ['iOS', 'Android'];

// Local selenium config
const commonConfigObj = {
browser: BROWSER_NAME,
url: 'http://localhost:8080',
restart: true,
waitForTimeout: DEFAULT_WAIT_TIME
};

const helperObj = {};
const isLocalBuild = typeof SAUCE_USERNAME === 'undefined';

if (isLocalBuild) {
helperObj.WebDriverIO = commonConfigObj;
} else {
// Common saucelab config
const sauceObj = {
host: 'ondemand.saucelabs.com',
port: 80,
user: SAUCE_USERNAME,
key: SAUCE_ACCESS_KEY,
desiredCapabilities: {
name: CI ? 'Travis cron' : require('os').userInfo().username, // eslint-disable-line global-require
build: TRAVIS_JOB_NUMBER,
'tunnel-identifier': TRAVIS_JOB_NUMBER,
browserName: BROWSER_NAME,
platform: BROWSER_PLATFORM
}
};

const mixedInSauceObj = Object.assign({}, commonConfigObj, sauceObj);
if (MOBILE_PLATFORMS.indexOf(BROWSER_PLATFORM) === -1) {
// webdriver (desktop)
Object.assign(sauceObj.desiredCapabilities, {
version: BROWSER_VERSION
});
helperObj.WebDriverIO = mixedInSauceObj;
} else {
// appium (mobile)
Object.assign(sauceObj.desiredCapabilities, {
platformVersion: PLATFORM_VERSION,
deviceName: DEVICE_NAME,
deviceOrientation: 'portrait',
appiumVersion: '1.7.2',
platformName: BROWSER_PLATFORM
});
helperObj.Appium = mixedInSauceObj;
}
}

exports.config = {
tests: './functional-tests/tests/*.js',
timeout: DEFAULT_WAIT_TIME,
output: './functional-tests/output',
helpers: helperObj,
include: {},
bootstrap: './functional-tests/helpers/cleanup.js',
teardown: './functional-tests/helpers/cleanup.js',
mocha: {},
name: 'box-annotations',
hooks: isLocalBuild ? [] : ['./functional-tests/helpers/eventHooks.js']
};
15 changes: 15 additions & 0 deletions functional-tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Running tests locally

## SauceLabs
1) Download the [saucelabs proxy](https://wiki.saucelabs.com/display/DOCS/Sauce+Connect+Proxy)
2) Run ```yarn functional-tests``` to start a local server on localhost:8000
3) Run the proxy ```./bin/sc -u SAUCELABS_USER_NAME -k SAUCELABS_ACCESS_KEY -N -i test``` to allow saucelabs to access your localhost
4) Run the tests
```SAUCE_USERNAME=SAUCELABS_USER_NAME SAUCE_ACCESS_KEY=SAUCELABS_ACCESS_KEY TRAVIS_JOB_NUMBER=TUNNEL_ID FILE_ID="285568802145" FILE_VERSION_ID="300497533713" node ./node_modules/codeceptjs/bin/codecept.js run --verbose``` where SAUCE_USERNAME, SAUCELABS_ACCESS_KEY can be found in saucelabs website. TUNNEL_ID is a unique identifier such as your username.

## Selenium (without SauceLabs)
1) Install selenium-standalone `npm install selenium-standalone@latest -g`
2) Install Selenium drivers `selenium-standalone install`
3) Start Selenium `selenium-standalone start`
4) In a separate terminal, build Preview `yarn run build`
5) Run functional tests `FILE_ID="285568802145" FILE_VERSION_ID="300497533713" yarn run functional-tests`
72 changes: 72 additions & 0 deletions functional-tests/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/* eslint-disable func-names */
/* eslint-disable prefer-arrow-callback */
/* eslint-disable require-jsdoc */
/* eslint-disable no-console */
const express = require('express');
const path = require('path');
const BoxSDK = require('box-node-sdk');

const {
FILE_ID,
ACCESS_TOKEN,
CLIENT_ID
} = process.env;

const app = express();
const server = app.listen(8080, () => console.log('Example app listening on port 8080!'));

// Set up SDK & client
const sdk = new BoxSDK({
clientID: CLIENT_ID,
clientSecret: 'NUH UH'
});
const client = sdk.getBasicClient(ACCESS_TOKEN);

app.use(express.static(path.join(__dirname, 'lib')));
app.set('view engine', 'pug');
app.set('views', path.join(__dirname, 'views'));

// this function is called when you want the server to die gracefully
// i.e. wait for existing connections
function gracefulShutdown() {
console.log('Received kill signal, shutting down gracefully.');
server.close(function() {
console.log('Closed out remaining connections.');
process.exit();
});

// if after
setTimeout(function() {
console.error('Could not close connections in time, forcefully shutting down');
process.exit();
}, 10*1000);
}

function errorCallback(err) {
console.log(err.response.body);
gracefulShutdown();
}

// viewed at http://localhost:8080
app.get('/', function(req, res) {
const url = `https://api.box.com/2.0/files/${FILE_ID}`;
const options = {
actor: {
id: '3504101558',
name: 'Kanye West'
}
};

client.exchangeToken(['item_preview'], url, options)
.then((tokenInfo) => {
// tokenInfo.accessToken contains the new annotator token
res.render('index', { token: tokenInfo.accessToken, file_id: FILE_ID });
})
.catch(errorCallback);
});

// listen for TERM signal .e.g. kill
process.on ('SIGTERM', gracefulShutdown);

// listen for INT signal e.g. Ctrl-C
process.on ('SIGINT', gracefulShutdown);
44 changes: 44 additions & 0 deletions functional-tests/helpers/cleanup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* eslint-disable func-names, prefer-arrow-callback, require-jsdoc, no-console */
const BoxSDK = require('box-node-sdk');

const {
FILE_ID,
FILE_VERSION_ID,
ACCESS_TOKEN,
CLIENT_ID
} = process.env;

const sdk = new BoxSDK({
clientID: CLIENT_ID,
clientSecret: 'NUH UH'
});
const client = sdk.getBasicClient(ACCESS_TOKEN);

function deleteAnnotation(annotation) {
const { id } = annotation;
client.del(`/annotations/${id}`, {}, function(err) {
if (err) {
// handle error
throw err;
}
console.log(`Annotation ID ${id} was deleted`);
});
}

module.exports = function() {
client.get(`/files/${FILE_ID}/annotations?version=${FILE_VERSION_ID}`, {}, function(err, response) {
if (err) {
// handle error
throw err;
}

const { entries } = response.body;
if (!entries) {
console.log('File does not have any existing annotations');
return;
}

console.log(`Deleting ${entries.length} annotations`);
entries.forEach(deleteAnnotation);
});
}
Loading

0 comments on commit db9a320

Please sign in to comment.