prvi
This commit is contained in:
198
ZpcBulletinBoard/wwwroot/lib/popper/README.md
Normal file
198
ZpcBulletinBoard/wwwroot/lib/popper/README.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# Popper: Realtime Cross-Browser Automation
|
||||
|
||||
The benefit of the ubiquity of the Web is also its pain point when it comes to accurate testing. It is common to see handwavy statements for browser compatibility, for example "IE9+", as if every other OS/platform combination will be just fine if IE9 "works"! For those who need more realistic data, this module makes it _much_ easier to test code earlier on in the development lifecycle, even in realtime.
|
||||
|
||||

|
||||
_Snapshot of Test Results for Ripple v0.3 on latest Chrome, Firefox, IE, Android and iOS_
|
||||
|
||||
## Features
|
||||
|
||||
* **Spawn** agents using BrowserStack/Sauce
|
||||
* **Multi-repo** testing, where changing one module might affect others (e.g microlibs in [utilise](https://github.com/utilise/utilise) or modules in [ripple v0.3](https://github.com/pemrouz/ripple))
|
||||
* **Aggregate** results per-repo, per-browser and globally in realtime
|
||||
* Rerun on **file change**
|
||||
* Open your own browser tabs to act as test agents, useful for **enterprise context**
|
||||
* Automatic **OS/Browser identification** and simple icons
|
||||
* **CLI** with convenient defaults
|
||||
* `.popper.yml` file for persisting test **config per repo**
|
||||
* Proxy console/error statements, making **remote debugging** on mobile devices a lot easier
|
||||
* **Force a rerun** on a particular agent
|
||||
* **View a snapshot of the results page** stream in as produced by the agent
|
||||
* **CI Integration** by having `npm test` run `popper` which will return exit cleanly if the tests pass in all defined browsers
|
||||
* **Increase CI Timeout** by setting `POPPER_TIMEOUT=milliseconds` to help investigate why a CI test is failiing (defaults to 20s)
|
||||
* **Optional Tunnel** to allow completely disabling the tunnelling via ngrok
|
||||
* Add your own [remote farm](https://github.com/pemrouz/popper/tree/0.1.0/src/farms)
|
||||
* Add your own [test runner](https://github.com/pemrouz/popper/tree/0.1.0/src/client)
|
||||
|
||||
See [roadmap issues label](https://github.com/pemrouz/popper/labels/roadmap) for upcoming features/idea.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# since this uses lots and lots of tiny libs, I recommend using npm3
|
||||
npm i -g popper # install globally
|
||||
npm i -D popper # install locally
|
||||
|
||||
# to run
|
||||
popper
|
||||
|
||||
# to also see logs from each browser in terminal window
|
||||
NODE_ENV=debug popper
|
||||
|
||||
# if you are using browserstack
|
||||
export BROWSERSTACK_USERNAME=...
|
||||
export BROWSERSTACK_KEY=...
|
||||
```
|
||||
|
||||
Once running, open a browser tab to `localhost:1945` (or the external ngrok URL) to run the tests, and keep open `localhost:1945/dashboard` to see the results as you continue to make changes. If you specified any `browsers`, they will be launched on BrowserStack and pointed to the test page.
|
||||
|
||||
When you run `popper` in a folder:
|
||||
|
||||
* If there is a `popper.js` file, it will run that ([example](https://github.com/pemrouz/ripple/blob/master/popper.js))
|
||||
* If there is a `popper.yml` file, it will use options from that ([example](https://github.com/utilise/utilise/blob/master/.popper.yml))
|
||||
* If there are any command-line arguments passed, those will take precedence in overriding the `.yml` config
|
||||
* If there is nothing, it will default convenient options so you can just jump into most repos and run `popper`
|
||||
|
||||
### CLI Options
|
||||
|
||||
You can set all the following options using via the CLI, YAML or JS API:
|
||||
|
||||
```
|
||||
usage: popper
|
||||
|
||||
options:
|
||||
-b, --browsers: browser to spawn and run tests on, defaults to none
|
||||
-t, --test: command to generate test bundle, defaults to "browserify test.js"
|
||||
-p, --port: port to run on, defaults to 1945
|
||||
-w, --watch: files to watch for changes, defaults to .
|
||||
-n, --notunnel: disable opening tunnel, defaults to open
|
||||
-l, --timeout: maximum time to wait in ci mode for results, defaults to POPPER_TIMEOUT or 20000
|
||||
-r, --runner: the runner to use, either mocha or tape, defatuls to mocha
|
||||
-f, --farm: the remote browser farm to spawn browsers in, defaults to browserstack
|
||||
```
|
||||
|
||||
### Default Options
|
||||
|
||||
If any of the options are missing from the local YAML config or CLI arguments, they will default to:
|
||||
|
||||
* Globals: `none`
|
||||
* Browsers: `none`
|
||||
* Test: `browserify test.js`
|
||||
* Port: `1945`
|
||||
* Watch: `.`
|
||||
* Disable Tunnel: `false`
|
||||
* Runner: `mocha`
|
||||
* Timeout: `process.env.POPPER_TIMEOUT || 20000`
|
||||
* Farm: `browserstack`
|
||||
|
||||
### YAML Options ([Example](https://github.com/utilise/utilise/blob/master/.popper.yml) | [Example](https://github.com/rijs/reactive/blob/master/.popper.yml))
|
||||
|
||||
```yaml
|
||||
# these will be added to the head
|
||||
globals:
|
||||
- <script src="https://cdn.polyfill.io/v1/polyfill.min.js"></script>
|
||||
- <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js" charset="utf-8"></script>
|
||||
- <script src="https://cdnjs.cloudflare.com/ajax/libs/chai/3.0.0/chai.min.js"></script>
|
||||
- <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.3/moment.min.js"></script>
|
||||
|
||||
# this is the command to generate the tests bundle on startup and after a file change detected
|
||||
tests: browserify ./node_modules/*/test.js
|
||||
-i moment
|
||||
-i colors
|
||||
-i jsdom
|
||||
-i chai
|
||||
-i d3
|
||||
-i ./node_modules/pause/test.js
|
||||
-i ./node_modules/send/test.js
|
||||
-i ./node_modules/file/test.js
|
||||
-i ./node_modules/via/test.js
|
||||
| sed -E "s/require\('moment'\)/window.moment/"
|
||||
| sed -E "s/require\('chai'\)/window.chai/"
|
||||
| sed -E "s/require\('d3'\)/window.d3/"
|
||||
| uglifyjs
|
||||
|
||||
# browsers to spawn in browserstack/sauce
|
||||
# can be wd capabilities object to specify os, device, version, etc: https://www.browserstack.com/automate/capabilities
|
||||
browsers:
|
||||
- ie9
|
||||
- android
|
||||
- iphone
|
||||
- opera
|
||||
- safari
|
||||
|
||||
# port to run on locally
|
||||
port: 1945
|
||||
|
||||
# glob(s) to watch for file changes
|
||||
watch: ./node_modules/*/index.js
|
||||
```
|
||||
|
||||
In this case, the test command will rebuild the project before bundling the tests after each file change.
|
||||
|
||||
```yaml
|
||||
globals:
|
||||
- <script src="https://cdn.polyfill.io/v1/polyfill.min.js"></script>
|
||||
- <script src="https://cdnjs.cloudflare.com/ajax/libs/chai/3.0.0/chai.min.js"></script>
|
||||
|
||||
tests: (npm run build > /dev/null) && browserify ./test.js
|
||||
-i colors
|
||||
-i chai
|
||||
| sed -E "s/require\('chai'\)/window.chai/"
|
||||
| uglifyjs
|
||||
|
||||
watch:
|
||||
- src
|
||||
- test.js
|
||||
```
|
||||
|
||||
### JS Options ([Example](https://github.com/pemrouz/ripple/blob/master/popper.js))
|
||||
|
||||
```js
|
||||
popper = require('popper')
|
||||
popper = popper({
|
||||
watch: ['src', 'test']
|
||||
, port: 19450
|
||||
, tests: stream // function that returns stream to be piped to the test bundle file
|
||||
, globals: string // string of global script tags to add
|
||||
, browsers: array // array of browsers to spawn
|
||||
})
|
||||
```
|
||||
|
||||
Popper uses Ripple under the hood. The JS API is particularly useful if you need to extend the available resources. For example, for testing Ripple itself and it's server/client synchronisation module, I use the following to reset test resources before each test:
|
||||
|
||||
```js
|
||||
popper.io.on('connection', function(socket){
|
||||
socket.on('beforeEach', function(){
|
||||
popper('foo' , 'bar', headers())
|
||||
popper('object' , { a:0 , b:1, c:2 }, headers())
|
||||
popper('array' , [{i:0}, {i:1},{i:2}], headers())
|
||||
popper({ name: 'proxy', body: [{i:0}, {i:1},{i:2}], headers: { to: to, from: from, 'cache-control': 'no-cache', silent: true, reactive: false }})
|
||||
popper('my-component' , component, headers())
|
||||
popper.sync(socket)()
|
||||
socket.emit('done')
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
##### Adding a Farm
|
||||
|
||||
If you'd like to add a new remote browser farm, you just need to add a folder under [`/farms`](https://github.com/pemrouz/popper/tree/0.1.0/src/farms) and then you can set the `farm` option (via JS, YAML or CLI) to the name of your farm. The folder should contain two things (see [browserstack](https://github.com/pemrouz/popper/tree/0.1.0/src/farms/browserstack) for an example):
|
||||
|
||||
* `browsers.json`: Mapping of shorthands (e.g. `ie9`) to browser capability objects. Since there is different properties across services (e.g. `browser` vs `browserName`), you should also specify the properties `_name`, `_version`, `_os` and `_os_version` where applicable, which is what will show up in the popper logs.
|
||||
|
||||
* `connect`: A [connect function](https://github.com/pemrouz/popper/blob/0.1.0/src/farms/browserstack/index.js#L7-L16), which will take in a `wd` instance and using the relevant environment variables, connect to the service. If it failed to connect, it should return false.
|
||||
|
||||
##### Adding a Runner
|
||||
|
||||
If you'd like to add a new test runner, you just need to add a folder [`/client`](https://github.com/pemrouz/popper/tree/0.1.0/src/client) and then you can set the `runner` option (via JS, YAML or CLI) to the name of your runner. The folder should contain two things (see [mocha](https://github.com/pemrouz/popper/tree/0.1.0/src/client/mocha) for an example):
|
||||
|
||||
* `index.html`: This is the HTML file that will be run on the test agent. You will also need a script (see `client.js`) that streams back the results of the test.
|
||||
|
||||
* `logs.html`: A HTML file that will be used from the dashboard (when you click "View Results") to format the read-only results of the tests run on a specific agent.
|
||||
|
||||
|
||||
# Acknowledgements
|
||||
|
||||
Thanks to BrowserStack and SauceLabs for providing a free open source account for testing.
|
||||
50
ZpcBulletinBoard/wwwroot/lib/popper/cli
Normal file
50
ZpcBulletinBoard/wwwroot/lib/popper/cli
Normal file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env node
|
||||
require('colors')
|
||||
var run = require('child_process').spawn
|
||||
, file = require('utilise/file')
|
||||
, is = require('utilise/is')
|
||||
, yaml = require('js-yaml').safeLoad
|
||||
, popper = require('.')
|
||||
, argv = require('minimist')(process.argv.slice(2))
|
||||
, port = argv.p || argv.port
|
||||
, browsers = argv.b || argv.browsers
|
||||
, tests = argv.t || argv.tests
|
||||
, help = argv.h || argv.help
|
||||
, notunnel = argv.n || argv.notunnel
|
||||
, timeout = argv.l || argv.timeout
|
||||
, runner = argv.r || argv.runner
|
||||
, farm = argv.f || argv.farm
|
||||
, exists = require('fs').existsSync
|
||||
, script = exists('popper.js')
|
||||
, config = exists('.popper') ? yaml(file('.popper'))
|
||||
: exists('popper.yml') ? yaml(file('popper.yml'))
|
||||
: exists('.popper.yml') ? yaml(file('.popper.yml'))
|
||||
: {}
|
||||
|
||||
if (help) return usage()
|
||||
if (browsers) config.browsers = is.str(browsers) ? browsers.split(',') : []
|
||||
if (tests) config.tests = tests
|
||||
if (port) config.port = port
|
||||
if (notunnel) config.notunnel = true
|
||||
if (timeout) config.timeout = timeout
|
||||
if (runner) config.runner = runner
|
||||
if (farm) config.farm = farm
|
||||
|
||||
return script ? run('sh', ['-c', 'node popper.js'], { stdio: 'inherit' })
|
||||
: popper(config)
|
||||
|
||||
function usage(){
|
||||
console.error('')
|
||||
console.error(' usage: popper')
|
||||
console.error('')
|
||||
console.error(' options:')
|
||||
console.error(' -b, --browsers: browser to spawn and run tests on, defaults to none')
|
||||
console.error(' -t, --test: command to generate test bundle, defaults to "browserify test.js"')
|
||||
console.error(' -p, --port: port to run on, defaults to 1945')
|
||||
console.error(' -w, --watch: files to watch for changes, defaults to .')
|
||||
console.error(' -n, --notunnel: disable opening tunnel, defaults to open')
|
||||
console.error(' -l, --timeout: maximum time to wait in ci mode for results, defaults to POPPER_TIMEOUT or 20000')
|
||||
console.error(' -r, --runner: the runner to use, either mocha or tape, defatuls to mocha')
|
||||
console.error(' -f, --farm: the remote browser farm to spawn browsers in, defaults to browserstack')
|
||||
process.exit(1)
|
||||
}
|
||||
19
ZpcBulletinBoard/wwwroot/lib/popper/client/dashboard.html
Normal file
19
ZpcBulletinBoard/wwwroot/lib/popper/client/dashboard.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Popper Dashboard</title>
|
||||
<script src="/utilise.js"></script>
|
||||
<script src="/ripple.pure.js"></script>
|
||||
<style>
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<dashboard-results></dashboard-results>
|
||||
</body>
|
||||
</html>
|
||||
50
ZpcBulletinBoard/wwwroot/lib/popper/client/mocha/client.js
Normal file
50
ZpcBulletinBoard/wwwroot/lib/popper/client/mocha/client.js
Normal file
@@ -0,0 +1,50 @@
|
||||
const core = require('rijs.core')
|
||||
, data = require('rijs.data')
|
||||
, sync = require('rijs.sync')
|
||||
|
||||
const ripple = sync(data(core()))
|
||||
, all = require('utilise/all')
|
||||
, raw = require('utilise/raw')
|
||||
, to = require('utilise/to')
|
||||
, con = window.console
|
||||
|
||||
// send tests-starting signal
|
||||
ripple.send('results', 'SAVE', {
|
||||
stats: { running: true }
|
||||
, suites: []
|
||||
, html: 'Test in progress..'
|
||||
})
|
||||
|
||||
// proxy errors back to terminal
|
||||
// window.onerror = (message, url, linenumber) =>
|
||||
// ripple.io.emit('global err', message, url, linenumber)
|
||||
|
||||
// proxy console logs back to terminal
|
||||
;['log', 'info', 'warn', 'error', 'debug'].map(m => {
|
||||
if (!con || !con[m]) return; // ie
|
||||
const sup = Function.prototype.bind.call(con[m], con)
|
||||
window.console[m] = function(){
|
||||
const args = to.arr(arguments)
|
||||
// ripple.io.emit('console', m, args.map(d => d))
|
||||
sup.apply && sup.apply(con, arguments)
|
||||
}
|
||||
})
|
||||
|
||||
// send final results back
|
||||
window.finish = function(){
|
||||
const stats = this.stats
|
||||
stats.running = false
|
||||
ripple.send('results', 'SAVE', {
|
||||
stats
|
||||
, suites: all('#mocha-report > .suite').map(suite)
|
||||
, html: raw('#mocha').innerHTML
|
||||
})
|
||||
}
|
||||
|
||||
function suite(s){
|
||||
return {
|
||||
name: raw('h1', s).textContent
|
||||
, total: '' + all('.test', s).length
|
||||
, failures: '' + all('.fail', s).length
|
||||
}
|
||||
}
|
||||
3130
ZpcBulletinBoard/wwwroot/lib/popper/client/mocha/client.min.js
vendored
Normal file
3130
ZpcBulletinBoard/wwwroot/lib/popper/client/mocha/client.min.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
17
ZpcBulletinBoard/wwwroot/lib/popper/client/mocha/index.html
Normal file
17
ZpcBulletinBoard/wwwroot/lib/popper/client/mocha/index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<title>Popper Agent (Mocha)</title>
|
||||
<link rel="stylesheet" href="/mocha.css" />
|
||||
<!-- { extra scripts } -->
|
||||
<script src="/mocha/client.min.js"></script>
|
||||
<script src="/mocha.js"></script>
|
||||
<script>mocha.setup("bdd")</script>
|
||||
<script src="/tests.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
<script>mocha.run().on('end', finish)</script>
|
||||
</body>
|
||||
</html>
|
||||
15
ZpcBulletinBoard/wwwroot/lib/popper/client/mocha/logs.html
Normal file
15
ZpcBulletinBoard/wwwroot/lib/popper/client/mocha/logs.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Popper Results Page (Mocha)</title>
|
||||
<link rel="stylesheet" href="/mocha.css" />
|
||||
<script src="/ripple.min.js"></script>
|
||||
<script src="/mocha.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha">
|
||||
<logs-view data="results"></logs-view>
|
||||
<div id="output"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
64
ZpcBulletinBoard/wwwroot/lib/popper/client/tape/client.js
Normal file
64
ZpcBulletinBoard/wwwroot/lib/popper/client/tape/client.js
Normal file
@@ -0,0 +1,64 @@
|
||||
const debounce = require('utilise/debounce')
|
||||
, escape = require('utilise/escape')
|
||||
, noop = require('utilise/noop')
|
||||
, raw = require('utilise/raw')
|
||||
, to = require('utilise/to')
|
||||
, core = require('rijs.core')
|
||||
, data = require('rijs.data')
|
||||
, sync = require('rijs.sync')
|
||||
|
||||
const ripple = sync(data(core()))
|
||||
, con = window.console
|
||||
, log = con ? Function.prototype.bind.call(con.log, con) : noop
|
||||
|
||||
var html = ''
|
||||
, running = true
|
||||
, failures = 0
|
||||
, passes = 0
|
||||
, tests = 0
|
||||
, name = 'All Tests'
|
||||
, output = raw('pre')
|
||||
|
||||
// send tests-starting signal
|
||||
ripple.send('results', 'SAVE', {
|
||||
stats: { running }
|
||||
, suites: []
|
||||
, html: 'Test in progress..'
|
||||
})
|
||||
|
||||
// proxy errors back to terminal
|
||||
// window.onerror = (message, url, linenumber) =>
|
||||
// ripple.io.emit('global err', message, url, linenumber)
|
||||
|
||||
// proxy console logs back to terminal
|
||||
;['log', 'info', 'warn', 'error', 'debug'].map(m => {
|
||||
if (!con || !con[m]) return; // ie
|
||||
const sup = Function.prototype.bind.call(con[m], con)
|
||||
window.console[m] = function(){
|
||||
const args = to.arr(arguments)
|
||||
// ripple.io.emit('console', m, args.map(d => d))
|
||||
sup.apply && sup.apply(con, arguments)
|
||||
}
|
||||
})
|
||||
|
||||
// stream results back
|
||||
var update = debounce(500)(function(){
|
||||
const stats = { running, tests, passes, failures }
|
||||
, suites = [{ name, failures, total: tests }]
|
||||
|
||||
output.innerHTML = html
|
||||
ripple.send('results', 'SAVE', { stats, suites, html })
|
||||
})
|
||||
|
||||
// listen on log
|
||||
;(window.console = window.console || {}).log = function(){
|
||||
const line = to.arr(arguments).join(' ')
|
||||
html += escape(line) + '\n'
|
||||
|
||||
if (-1 === includes('# tests')(line)) running = false
|
||||
if (-1 === includes('ok ')(line)) { passes++; tests++ }
|
||||
if (-1 === includes('not ok ')(line)) { failures++; tests++ }
|
||||
|
||||
if (line.match(/^(?!.*\[ri\/)/)) update()
|
||||
log.apply(console, arguments)
|
||||
}
|
||||
3173
ZpcBulletinBoard/wwwroot/lib/popper/client/tape/client.min.js
vendored
Normal file
3173
ZpcBulletinBoard/wwwroot/lib/popper/client/tape/client.min.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
15
ZpcBulletinBoard/wwwroot/lib/popper/client/tape/index.html
Normal file
15
ZpcBulletinBoard/wwwroot/lib/popper/client/tape/index.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta charset="utf-8">
|
||||
<title>Popper Agent (Tape)</title>
|
||||
<!-- { extra scripts } -->
|
||||
<script src="/utilise.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<pre></pre>
|
||||
</body>
|
||||
</html>
|
||||
<script src="/tape/client.min.js"></script>
|
||||
<script src="/tests.js"></script>
|
||||
13
ZpcBulletinBoard/wwwroot/lib/popper/client/tape/logs.html
Normal file
13
ZpcBulletinBoard/wwwroot/lib/popper/client/tape/logs.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Popper Results Page (Tape)</title>
|
||||
<script src="/ripple.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<code>
|
||||
<logs-view data="results"></logs-view>
|
||||
<pre id="output"></pre>
|
||||
</code>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"ie9": {
|
||||
"browserName": "internet explorer"
|
||||
, "version": "9"
|
||||
, "platform": "WINDOWS"
|
||||
, "_name": "ie"
|
||||
, "_version": "9"
|
||||
, "_os": "windows"
|
||||
}
|
||||
, "ie11": {
|
||||
"browserName": "internet explorer"
|
||||
, "version": "11"
|
||||
, "platform": "WINDOWS"
|
||||
, "_name": "ie"
|
||||
, "_version": "11"
|
||||
, "_os": "windows"
|
||||
}
|
||||
, "edge": {
|
||||
"browserName": "edge"
|
||||
, "platform": "WINDOWS"
|
||||
, "_name": "ie"
|
||||
, "_os": "windows"
|
||||
}
|
||||
, "firefox": {
|
||||
"browserName": "firefox"
|
||||
, "platform": "WINDOWS"
|
||||
, "_name": "firefox"
|
||||
, "_os": "windows"
|
||||
}
|
||||
, "chrome": {
|
||||
"browserName": "chrome"
|
||||
, "platform": "WINDOWS"
|
||||
, "_name": "chrome"
|
||||
, "_os": "windows"
|
||||
}
|
||||
, "android": {
|
||||
"browserName": "android"
|
||||
, "device": "Google Nexus 5"
|
||||
, "platform": "ANDROID"
|
||||
, "_name": "android"
|
||||
, "_os": "nexus"
|
||||
, "_os_version": "5"
|
||||
}
|
||||
, "iphone": {
|
||||
"browserName": "iPhone"
|
||||
, "device": "iPhone 5"
|
||||
, "platform": "MAC"
|
||||
, "_name": "safari"
|
||||
, "_os": "iphone"
|
||||
, "_os_version": "5"
|
||||
}
|
||||
, "opera": {
|
||||
"browserName": "opera"
|
||||
, "platform": "WINDOWS"
|
||||
, "_name": "opera"
|
||||
, "_os": "windows"
|
||||
}
|
||||
, "safari": {
|
||||
"browserName": "safari"
|
||||
, "platform": "MAC"
|
||||
, "_name": "safari"
|
||||
, "_os": "osx"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
const browsers = require('./browsers.json')
|
||||
, err = require('utilise/err')('[popper][browserstack]')
|
||||
|
||||
module.exports = { browsers, connect }
|
||||
|
||||
function connect(wd) {
|
||||
const env = process.env
|
||||
, key = env.BROWSERSTACK_KEY
|
||||
, user = env.BROWSERSTACK_USERNAME
|
||||
, host = 'hub.browserstack.com'
|
||||
|
||||
return !user || !key
|
||||
? (err('Please provide your BrowserStack Credentials'), false)
|
||||
: wd.remote(host, 80, user, key)
|
||||
}
|
||||
4
ZpcBulletinBoard/wwwroot/lib/popper/farms/index.js
Normal file
4
ZpcBulletinBoard/wwwroot/lib/popper/farms/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
browserstack: require('./browserstack')
|
||||
, saucelabs: require('./saucelabs')
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"ie9": {
|
||||
"browserName": "internet explorer"
|
||||
, "version": "9.0"
|
||||
, "platform": "Windows 7"
|
||||
, "_name": "ie"
|
||||
, "_version": "9"
|
||||
, "_os": "windows"
|
||||
}
|
||||
, "ie11": {
|
||||
"browserName": "internet explorer"
|
||||
, "version": "11.0"
|
||||
, "platform": "Windows 7"
|
||||
, "_name": "ie"
|
||||
, "_version": "11"
|
||||
, "_os": "windows"
|
||||
}
|
||||
, "edge": {
|
||||
"browserName": "MicrosoftEdge"
|
||||
, "platform": "Windows 10"
|
||||
, "_name": "ie"
|
||||
, "_os": "windows"
|
||||
}
|
||||
, "firefox": {
|
||||
"browserName": "firefox"
|
||||
, "platform": "WINDOWS"
|
||||
, "_name": "firefox"
|
||||
, "_os": "windows"
|
||||
}
|
||||
, "chrome": {
|
||||
"browserName": "chrome"
|
||||
, "platform": "WINDOWS"
|
||||
, "_name": "chrome"
|
||||
, "_os": "windows"
|
||||
}
|
||||
, "android": {
|
||||
"browserName": "android"
|
||||
, "device": "Google Nexus 5"
|
||||
, "platform": "ANDROID"
|
||||
, "_name": "android"
|
||||
, "_os": "nexus"
|
||||
, "_os_version": "5"
|
||||
}
|
||||
, "iphone": {
|
||||
"browserName": "iPhone"
|
||||
, "device": "iPhone 5"
|
||||
, "platform": "MAC"
|
||||
, "_name": "safari"
|
||||
, "_os": "iphone"
|
||||
, "_os_version": "5"
|
||||
}
|
||||
, "opera": {
|
||||
"browserName": "opera"
|
||||
, "platform": "WINDOWS"
|
||||
, "_name": "opera"
|
||||
, "_os": "windows"
|
||||
}
|
||||
, "safari": {
|
||||
"browserName": "safari"
|
||||
, "platform": "MAC"
|
||||
, "_name": "safari"
|
||||
, "_os": "osx"
|
||||
}
|
||||
}
|
||||
39
ZpcBulletinBoard/wwwroot/lib/popper/farms/saucelabs/index.js
Normal file
39
ZpcBulletinBoard/wwwroot/lib/popper/farms/saucelabs/index.js
Normal file
@@ -0,0 +1,39 @@
|
||||
const { extend, str } = require('utilise/pure')
|
||||
, browsers = require('./browsers.json')
|
||||
, err = require('utilise/err')('[popper][saucelabs]')
|
||||
, log = require('utilise/log')('[popper][saucelabs]')
|
||||
|
||||
module.exports = { browsers, connect, status, parse }
|
||||
|
||||
function connect(wd) {
|
||||
const env = process.env
|
||||
, key = env.SAUCE_ACCESS_KEY
|
||||
, user = env.SAUCE_USERNAME
|
||||
, host = 'ondemand.saucelabs.com'
|
||||
|
||||
return !user || !key
|
||||
? (err('Please provide your SauceLabs Credentials'), false)
|
||||
: wd.remote(host, 80, user, key)
|
||||
}
|
||||
|
||||
function status(browser, platform) {
|
||||
browser.vm
|
||||
.sauceJobStatus(browser.passed, e => {
|
||||
e ? err(e) : log(
|
||||
'status updated'
|
||||
, platform.uid.bold
|
||||
, str(browser.passed)[browser.passed ? 'green' : 'red']
|
||||
, str(browser.build).grey
|
||||
)
|
||||
browser.vm.quit()
|
||||
})
|
||||
}
|
||||
|
||||
function parse(opts) {
|
||||
return extend(opts)({
|
||||
'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER
|
||||
, build: process.env.TRAVIS_BUILD_NUMBER || ~~(Math.random()*100000000)
|
||||
, username: process.env.SAUCE_USERNAME
|
||||
, accessKey: process.env.SAUCE_ACCESS_KEY
|
||||
})
|
||||
}
|
||||
296
ZpcBulletinBoard/wwwroot/lib/popper/index.js
Normal file
296
ZpcBulletinBoard/wwwroot/lib/popper/index.js
Normal file
@@ -0,0 +1,296 @@
|
||||
module.exports = function popper({
|
||||
tests = 'browserify test.js'
|
||||
, farm = 'browserstack'
|
||||
, notunnel = false
|
||||
, runner = 'mocha'
|
||||
, browsers = []
|
||||
, globals = ''
|
||||
, port = 1945
|
||||
, watch = '.'
|
||||
, opts = {}
|
||||
, timeout
|
||||
, ripple
|
||||
} = {}){
|
||||
|
||||
// defaults
|
||||
const wait = debounce(timeout = timeout || +env.POPPER_TIMEOUT || 20000)(quit)
|
||||
, maxRetries = 3
|
||||
|
||||
ripple = (ripple || rijs)(extend({ dir, port })(opts))
|
||||
resdir(ripple, dir)
|
||||
browsers = browsers
|
||||
.map(canonical(farm))
|
||||
.filter(Boolean)
|
||||
|
||||
// define data resources
|
||||
ripple('results', {}, { from })
|
||||
ripple('totals' , {})
|
||||
|
||||
// watch files
|
||||
if (!isCI && watch) {
|
||||
log('watching', watch)
|
||||
|
||||
chokidar.watch(watch, {
|
||||
ignored: [/^\.(.*)[^\/\\]/, /[\/\\]\./, /node_modules(.+)popper/]
|
||||
, ignoreInitial: true
|
||||
, usePolling: false
|
||||
, depth: 5
|
||||
})
|
||||
.on('change', debounce(generate))
|
||||
}
|
||||
|
||||
// icons
|
||||
ripple(require('browser-icons'))
|
||||
|
||||
// limit dashboard resources
|
||||
ripple.to = limit(ripple.to)
|
||||
|
||||
// proxy errors and register agent details
|
||||
ripple.server.on('connected', connected)
|
||||
|
||||
// serve assets
|
||||
ripple.server.express
|
||||
.use(compression())
|
||||
.use('/utilise.min.js', send(local('utilise', 'utilise.min.js')))
|
||||
.use('/utilise.js' , send(local('utilise', 'utilise.js')))
|
||||
.use('/mocha.css' , send(local('mocha', 'mocha.css')))
|
||||
.use('/mocha.js' , send(local('mocha', 'mocha.js')))
|
||||
.use('/chai.js' , send(local('chai', 'chai.js')))
|
||||
.use('/dashboard/:id' , send(local(`./client/${runner}/logs.html`)))
|
||||
.use('/dashboard' , send(local('./client/dashboard.html')))
|
||||
.use('/' , serve(local('./client')))
|
||||
.use('/' , index())
|
||||
|
||||
return generate()
|
||||
, spawn()
|
||||
, ripple
|
||||
|
||||
function index(){
|
||||
const head = is.arr(globals) ? globals.join('\n') : globals
|
||||
, html = file(local(`./client/${runner}/index.html`))
|
||||
.replace('<!-- { extra scripts } -->', head || '')
|
||||
|
||||
return (req, res) => res.send(html)
|
||||
}
|
||||
|
||||
function generate() {
|
||||
log('generating tests')
|
||||
|
||||
const bundle = write(local('./client/tests.js'))
|
||||
, stream = is.fn(tests)
|
||||
? tests()
|
||||
: run('sh', ['-c', tests], { stdio: 'pipe' })
|
||||
|
||||
if (stream.stderr)
|
||||
stream.stderr.pipe(process.stderr)
|
||||
|
||||
;((stream.stdout || stream)
|
||||
.on('end', debounce(500)(reload))
|
||||
.pipe(bundle)
|
||||
.flow || noop)()
|
||||
}
|
||||
|
||||
function from(req){
|
||||
return req.data.type == 'RERUN' ? reload(req.data.value)
|
||||
: req.data.type == 'SAVE' ? save(req.socket.platform, req.data.value)
|
||||
: false
|
||||
}
|
||||
|
||||
function save(platform, result) {
|
||||
const { uid } = platform
|
||||
, results = ripple('results')
|
||||
, retries = uid in results ? results[uid].retries : 0
|
||||
|
||||
log('received result from', uid)
|
||||
result.platform = platform
|
||||
result.retries = retries
|
||||
update(uid, result)(ripple('results'))
|
||||
totals()
|
||||
ci(result)
|
||||
}
|
||||
|
||||
function ci(r) {
|
||||
if (!isCI || r.stats.running) return
|
||||
|
||||
const browser = browsers
|
||||
.filter(d => {
|
||||
if (d._name && d._name !== r.platform.name) return false
|
||||
if (d._version && d._version !== r.platform.version) return false
|
||||
if (d._os && d._os !== r.platform.os.name) return false
|
||||
if (d._os_version && d._os_version !== r.platform.os.version) return false
|
||||
return true
|
||||
})
|
||||
.pop()
|
||||
|
||||
if (!browser) return log('result not in matrix'.red, r.platform.uid)
|
||||
|
||||
browser.passed_by = r.platform.uid
|
||||
browser.passed = !r.stats.failures
|
||||
browser.passed
|
||||
? log('browser passed:', r.platform.uid.green.bold)
|
||||
: err('browser failed:', r.platform.uid.red.bold)
|
||||
|
||||
if (!browser.passed && r.retries < maxRetries)
|
||||
return log('retrying'.yellow, r.platform.uid, ++r.retries, '/', str(maxRetries).grey)
|
||||
, reload(r.platform.uid)
|
||||
|
||||
if (farms[farm].status)
|
||||
farms[farm].status(browser, r.platform)
|
||||
|
||||
const target = browsers.length
|
||||
, passed = browsers.filter(by('passed')).length
|
||||
, finished = browsers.filter(by('passed_by')).length
|
||||
|
||||
log('ci targets', str(passed).green.bold, '/', str(target).grey)
|
||||
|
||||
target === passed ? time(3000, d => process.exit(0))
|
||||
: target === finished ? time(3000, d => (!env.POPPER_TIMEOUT && process.exit(1)))
|
||||
: wait()
|
||||
}
|
||||
|
||||
function connected(socket){
|
||||
socket.platform = parse(socket)
|
||||
socket.type = socket.handshake.url == '/dashboard' ? 'dashboard' : 'agent'
|
||||
log('connected', socket.platform.uid.green, socket.type.grey)
|
||||
|
||||
socket.on('global err', (message, url, linenumber) => err('Global error: ', socket.platform.uid.bold, message, url, linenumber))
|
||||
|
||||
if (debug)
|
||||
socket.on('console', function(){ log(socket.platform.uid.bold, 'says:', '', arguments[0], to.arr(arguments[1]).map(str).join(' ')) })
|
||||
}
|
||||
|
||||
function quit(){
|
||||
log('no updates received for', timeout/1000, 'seconds. timing out..')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
function reload(uid) {
|
||||
const uids = uid ? [uid] : ripple.server.ws.sockets.map(d => d.platform.uid)
|
||||
|
||||
uids
|
||||
.map(uid => update(`${uid}.stats.running`, true)(ripple('results')))
|
||||
|
||||
const agents = ripple.server.ws.sockets
|
||||
.filter(not(by('handshake.url', '/dashboard')))
|
||||
.filter(by('platform.uid', is.in(uids)))
|
||||
.map(emitReload)
|
||||
.length
|
||||
|
||||
log('reloading', str(agents).cyan, 'agents', uid || '')
|
||||
}
|
||||
|
||||
function totals() {
|
||||
const res = values(ripple('results'))
|
||||
return ripple('totals', {
|
||||
tests: str(res.map(key('stats.tests')).filter(Boolean).pop() || '?')
|
||||
, browsers: str(res.length)
|
||||
, passing: str(res.map(key('stats.failures')).filter(is(0)).length || '0')
|
||||
})
|
||||
}
|
||||
|
||||
function spawn(){
|
||||
ripple.server.once('listening').then(() => {
|
||||
log('running on port', ripple.server.http.address().port)
|
||||
!notunnel && require('ngrok').connect(ripple.server.http.address().port, (e, url) => {
|
||||
log('tunnelling', url && url.magenta)
|
||||
return e ? err('error setting up reverse tunnel', e.stack)
|
||||
: browsers.map(boot(farm)(url))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const { values, key, str, not, by, grep, lo, is, debounce, extend, falsy, send, file, noop, update, identity, time, includes } = require('utilise/pure')
|
||||
, write = require('fs').createWriteStream
|
||||
, run = require('child_process').spawn
|
||||
, { stringify } = require('cryonic')
|
||||
, { resolve } = require('path')
|
||||
, compression = require('compression')
|
||||
, browserify = require('browserify')
|
||||
, platform = require('platform')
|
||||
, chokidar = require('chokidar')
|
||||
, express = require('express')
|
||||
, resdir = require('rijs.resdir')
|
||||
, serve = require('serve-static')
|
||||
, farms = require('./farms')
|
||||
, wd = require('wd')
|
||||
, rijs = opts => require('rijs.npm')(require('rijs')(opts))
|
||||
|
||||
const log = require('utilise/log')('[popper]')
|
||||
, err = require('utilise/err')('[popper]')
|
||||
, old = grep(console, 'log', /^(?!.*\[ri\/)/)
|
||||
, env = process.env
|
||||
, dir = __dirname
|
||||
, isCI = env.CI === 'true'
|
||||
, debug = lo(env.NODE_ENV) == 'debug'
|
||||
|
||||
const heartbeat = vm => setInterval(d => vm.eval('', e => { if (e) console.error(e) }), 30000)
|
||||
|
||||
const canonical = farm => browser => is.str(browser)
|
||||
? farms[farm].browsers[browser]
|
||||
: browser
|
||||
|
||||
const local = (module, file) => {
|
||||
const base = !file ? __dirname : require.resolve(module)
|
||||
, read = !file ? module : '../'+file
|
||||
return resolve(base, read)
|
||||
}
|
||||
|
||||
const emitReload = socket => socket.send(stringify({ data: { exec: () => location.reload() }}))
|
||||
|
||||
const parse = socket => {
|
||||
const ua = socket.handshake.headers['user-agent']
|
||||
, p = platform.parse(ua)
|
||||
, o = {
|
||||
name: lo(p.name)
|
||||
, version: major(p.version)
|
||||
, os: {
|
||||
name: lo(p.os.family.split(' ').shift())
|
||||
, version: major(p.os.version, p.os.family)
|
||||
}
|
||||
}
|
||||
|
||||
if (o.os.name == 'os') o.os.name = 'osx'
|
||||
if (o.name == 'chrome mobile') o.name = 'chrome'
|
||||
if (o.name == 'microsoft edge') o.name = 'ie'
|
||||
|
||||
const uid = o.name
|
||||
+ '-' + o.version
|
||||
+ '-' + o.os.name
|
||||
+ '-' + o.os.version
|
||||
|
||||
o.uid = uid
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
const major = (v, f) =>
|
||||
v ? v.split('.').shift()
|
||||
: includes('xp')(lo(f)) ? 'xp'
|
||||
: '?'
|
||||
|
||||
const limit = next => (req, socket) => {
|
||||
return socket.handshake.url == '/dashboard' ? next(req, socket) : false
|
||||
}
|
||||
|
||||
const boot = farm => url => opts => {
|
||||
const { _name = '?', _version = '?', _os = '?' } = opts
|
||||
, { connect, parse = identity } = farms[farm]
|
||||
, id = `${_name.cyan} ${_version.cyan} on ${_os}`
|
||||
, vm = opts.vm = connect(wd)
|
||||
|
||||
if (!vm) err('failed to connect to ' + farm), process.exit(1)
|
||||
|
||||
log(`booting up ${id}`)
|
||||
|
||||
vm.init(parse(opts), e => {
|
||||
if (e) return err(e, id)
|
||||
log('initialised', id)
|
||||
vm.get(url, e => {
|
||||
if (e) return err(e, id)
|
||||
log('opened to test page', id.cyan)
|
||||
heartbeat(vm)
|
||||
})
|
||||
})
|
||||
}
|
||||
50
ZpcBulletinBoard/wwwroot/lib/popper/package.json
Normal file
50
ZpcBulletinBoard/wwwroot/lib/popper/package.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "popper",
|
||||
"main": "index.js",
|
||||
"version": "1.0.1",
|
||||
"description": "Realtime Cross-Browser Automation",
|
||||
"dependencies": {
|
||||
"browser-icons": "*",
|
||||
"browserify": "^12.0.0",
|
||||
"buble": "^0.16.0",
|
||||
"chai": "*",
|
||||
"chokidar": "*",
|
||||
"colors": "^1.1.2",
|
||||
"compression": "*",
|
||||
"cryonic": "^1.0.0",
|
||||
"express": "^4.12.4",
|
||||
"js-yaml": "*",
|
||||
"minimist": "*",
|
||||
"mocha": "*",
|
||||
"platform": "*",
|
||||
"rijs.sync": "*",
|
||||
"rijs.core": "*",
|
||||
"rijs.data": "*",
|
||||
"rijs.npm": "*",
|
||||
"rijs.css": "*",
|
||||
"rijs.fn": "*",
|
||||
"rijs": "*",
|
||||
"rijs.resdir": "*",
|
||||
"serve-static": "*",
|
||||
"utilise": "*",
|
||||
"wd": "*"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"ngrok": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"uglifyjs": "^2.4.10"
|
||||
},
|
||||
"author": "Pedram Emrouznejad (https://github.com/pemrouz)",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/pemrouz/popper.git"
|
||||
},
|
||||
"license": "pemrouz.mit-license.org",
|
||||
"bin": "./cli",
|
||||
"scripts": {
|
||||
"build": "find ./client/*/client.js -exec sh -c \"FILE={}; RUNNER=${FILE:9:-10}; echo bundling client: $RUNNER; buble ./client/$RUNNER/client.js | browserify - -i utilise -i express -i colors > client/$RUNNER/client.min.js\" ';'",
|
||||
"version": "npm run build && git add -A",
|
||||
"postversion": "git push && git push --tags"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
@import url(https://fonts.googleapis.com/css?family=Roboto:500,100,300,700);
|
||||
|
||||
:host,
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box; }
|
||||
|
||||
:host {
|
||||
font-family: Roboto, verdana;
|
||||
font-weight: 300;
|
||||
text-align: center;
|
||||
background: #171d25;
|
||||
color: white;
|
||||
cursor: default;
|
||||
-ms-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
overflow: hidden;
|
||||
padding: 40px 60px 0px;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%; }
|
||||
|
||||
:host(.has-results) {
|
||||
white-space: nowrap;
|
||||
position: relative; }
|
||||
|
||||
:host(.has-results)::before {
|
||||
content: '';
|
||||
width: calc(100% + 60px);
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 141px;
|
||||
left: -30px;
|
||||
border-bottom: 1px #1583ae solid; }
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box; }
|
||||
|
||||
.no-results {
|
||||
font-size: 24px;
|
||||
opacity: 0.3;
|
||||
font-weight: 100;
|
||||
position: absolute;
|
||||
width: 300px;
|
||||
height: 30px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-left: -150px;
|
||||
margin-top: -15px; }
|
||||
|
||||
.column {
|
||||
width: 160px;
|
||||
vertical-align: top;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
position: relative; }
|
||||
|
||||
.suites {
|
||||
padding-top: 120px; }
|
||||
|
||||
.totals {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0px;
|
||||
line-height: 29px;
|
||||
font-size: 21px;
|
||||
text-align: left;
|
||||
font-weight: 700; }
|
||||
|
||||
:host(.has-results) .totals {
|
||||
display: block; }
|
||||
|
||||
[label]::after {
|
||||
content: attr(label);
|
||||
margin-left: 42px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
opacity: 0.5;
|
||||
font-weight: 100;
|
||||
font-size: 14px;
|
||||
line-height: 26px; }
|
||||
|
||||
.browser {
|
||||
border: 1px solid transparent;
|
||||
/*transition: 100ms;*/
|
||||
margin-left: -3px; }
|
||||
|
||||
.browser:hover {
|
||||
border-color: #1583ae;
|
||||
background: rgba(0,0,0,0.1) }
|
||||
|
||||
.summary {
|
||||
position: absolute;
|
||||
top: 44px;
|
||||
width: 80px;
|
||||
right: 11px;
|
||||
font-size: 31px; }
|
||||
|
||||
a {
|
||||
position: absolute;
|
||||
top: 11px;
|
||||
font-size: 10px;
|
||||
width: 80px;
|
||||
right: 11px;
|
||||
font-weight: 500;
|
||||
color: #146586;
|
||||
cursor: pointer;}
|
||||
|
||||
a:hover {
|
||||
color: #13b6f4;
|
||||
text-decoration: underline; }
|
||||
a.view-tests {
|
||||
margin-top: 12px; }
|
||||
a.disabled {
|
||||
pointer-events: none;
|
||||
color: grey;
|
||||
opacity: 0.6; }
|
||||
|
||||
.platform {
|
||||
margin: 0;
|
||||
margin-bottom: 20px;
|
||||
height: 100px;
|
||||
font-size: 28px;
|
||||
line-height: 48px;
|
||||
border: #072c48 solid;
|
||||
border-width: 0 1px;
|
||||
padding: 0 13px; }
|
||||
|
||||
[version] {
|
||||
display: block;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin: 0;
|
||||
fill: white;
|
||||
position: relative;
|
||||
opacity: 0.3; }
|
||||
|
||||
[version]::before {
|
||||
content: attr(version);
|
||||
position: absolute;
|
||||
top: -15px;
|
||||
right: -24px;
|
||||
width: 30px;
|
||||
text-align: center;
|
||||
opacity: 0.5;
|
||||
font-size: 11px;
|
||||
font-weight: 300; }
|
||||
|
||||
[version].os {
|
||||
width: 15px;
|
||||
height: 44px;
|
||||
opacity: 0.1;
|
||||
margin: 0 8px; }
|
||||
|
||||
[version].os::before {
|
||||
text-transform: uppercase;
|
||||
top: -7px;
|
||||
right: -22px;
|
||||
font-size: 8px;
|
||||
opacity: 1; }
|
||||
|
||||
.error {
|
||||
font-weight: 700 !important;
|
||||
color: #c00 !important }
|
||||
|
||||
.result:hover {
|
||||
font-weight: 500;
|
||||
background: #2a3340;
|
||||
opacity: 1; }
|
||||
|
||||
.suite {
|
||||
font-weight: 300; }
|
||||
|
||||
.suite, .result {
|
||||
font-weight: 100;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
opacity: 0.5; }
|
||||
|
||||
.result::after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 2000px;
|
||||
height: 30px;
|
||||
position: absolute;
|
||||
background: rgba(0,0,0,0.1);
|
||||
left: -1000px;
|
||||
margin-top: -30px;
|
||||
pointer-events: none;
|
||||
opacity: 0; }
|
||||
|
||||
.result:hover::after {
|
||||
opacity: 1; }
|
||||
|
||||
.is-running .result > * {
|
||||
opacity: 0; }
|
||||
|
||||
.is-running .result::before {
|
||||
display: block;
|
||||
content: '...';
|
||||
height: 30px;
|
||||
position: absolute;
|
||||
width: 158px;
|
||||
z-index: 10; }
|
||||
|
||||
.result i {
|
||||
font-size: 9px;
|
||||
position: absolute;
|
||||
right: 45px;
|
||||
margin-top: -2px;
|
||||
color: rgba(255,255,255,0.2);
|
||||
font-weight: 700;
|
||||
font-style: normal; }
|
||||
|
||||
.result .delim {
|
||||
margin-top: -1px;
|
||||
right: 40px; }
|
||||
|
||||
.result .total {
|
||||
margin-top: 3px;
|
||||
right: 29px;
|
||||
text-align: left;
|
||||
width: 10px; }
|
||||
@@ -0,0 +1,126 @@
|
||||
module.exports = class dashboardResults {
|
||||
init(node){
|
||||
ripple
|
||||
.subscribe(['results', 'totals'])
|
||||
.map(([results, totals]) => Object.assign(node.state, { results, totals }))
|
||||
.map(d => node.draw())
|
||||
// .until(node.once('removed'))
|
||||
}
|
||||
|
||||
async render(node, { results = {}, totals = [] }){
|
||||
const [ once ] = await ripple.get('npm', ['utilise/once'])
|
||||
, { send } = ripple
|
||||
, o = once(node)
|
||||
, suites = first(values(results).filter(key('suites.length')))
|
||||
|
||||
o.classed('has-results', values(results).length)
|
||||
|
||||
o('.no-results', !values(results).length)
|
||||
.text('No results available yet')
|
||||
|
||||
o('.suites.column', suites || 1)
|
||||
|
||||
o('.suites')
|
||||
('.totals', totals)
|
||||
|
||||
o('.totals')
|
||||
('.tests', key('tests'))
|
||||
.text(String)
|
||||
.attr('label', 'Tests')
|
||||
o('.totals')
|
||||
('.browsers', key('browsers'))
|
||||
.text(String)
|
||||
.attr('label', 'Browsers')
|
||||
o('.totals')
|
||||
('.passing', key('passing'))
|
||||
.text(String)
|
||||
.attr('label', 'Passing')
|
||||
|
||||
o('.suites')
|
||||
('.suite', key('suites'))
|
||||
.text(key('name'))
|
||||
|
||||
o('.browser.column', values(results))
|
||||
.classed('is-running', key('stats.running'))
|
||||
|
||||
o('.browser')
|
||||
('h1.platform', key('platform'))
|
||||
|
||||
o('.platform')
|
||||
('[version]', [])
|
||||
|
||||
o('.platform')
|
||||
(iconOS, 1)
|
||||
.attr('version', key('os.version'))
|
||||
.classed('os', 1)
|
||||
|
||||
o('.platform')
|
||||
(iconBrowser, 1)
|
||||
.attr('version', key('version'))
|
||||
|
||||
o('.browser')
|
||||
('.summary', key('stats'))
|
||||
.text(formatSummary)
|
||||
|
||||
o('.browser')
|
||||
('a.run-tests', key('stats'))
|
||||
.text('Rerun')
|
||||
.classed('disabled', key('running'))
|
||||
.on('click.rerun', rerun)
|
||||
|
||||
o('.browser')
|
||||
('a.view-tests', key('platform.uid'))
|
||||
.text('View Results')
|
||||
.attr('target', '_blank')
|
||||
.attr('href', viewLink)
|
||||
|
||||
o('.browser')
|
||||
('.result', proxy(key('suites'), allSuites))
|
||||
('span', 1)
|
||||
.text(formatResult)
|
||||
.classed('error', by('failures', not(is('0'))))
|
||||
|
||||
o('.result')
|
||||
('i.fails', proxy(key('failures'), str))
|
||||
.classed('error', not(is('0')))
|
||||
.text(String)
|
||||
o('.result')
|
||||
('i.delim', '/')
|
||||
.text(String)
|
||||
o('.result')
|
||||
('i.total', key('total'))
|
||||
.text(String)
|
||||
|
||||
function rerun(d) {
|
||||
var uid = from.parent.call(this, 'platform').uid
|
||||
send('results', 'RERUN', uid)
|
||||
// update(uid + '.stats.running', true)(ripple('results'))
|
||||
}
|
||||
|
||||
function allSuites(d) {
|
||||
return d.length ? d : (key('suites')(suites) || []).map(wrap({}))
|
||||
}
|
||||
|
||||
function iconBrowser(d) {
|
||||
return 'icon-' + d.name
|
||||
}
|
||||
|
||||
function iconOS(d) {
|
||||
return 'icon-' + d.os.name
|
||||
}
|
||||
|
||||
function viewLink(d){
|
||||
return '/dashboard/' + d
|
||||
}
|
||||
|
||||
function formatSummary(d){
|
||||
return d.passes && d.tests ? Math.round(d.passes/d.tests*1000)/10+'%' : '?'
|
||||
}
|
||||
|
||||
function formatResult(d){
|
||||
return !str(d.total) || !str(d.failures)
|
||||
? '...'
|
||||
: Math.round((d.total-d.failures)/d.total*1000)/10 + '%'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = function logsView(node, { results }){
|
||||
const id = location.pathname.split('dashboard/').pop().replace(/-$/, '-?')
|
||||
|
||||
raw('#output').innerHTML = results[id]
|
||||
? results[id].html
|
||||
: 'No connected agent yet..'
|
||||
}
|
||||
Reference in New Issue
Block a user