Skip to content
Snippets Groups Projects
Commit 099799cd authored by Martin Donath's avatar Martin Donath Committed by GitHub
Browse files

Merge pull request #147 from squidfunk/chore/setup-gemini-test-environment

Setup visual regression testing
parents f40585f5 854663ff
Branches
Tags
No related merge requests found
Showing
with 585 additions and 43 deletions
...@@ -18,7 +18,11 @@ ...@@ -18,7 +18,11 @@
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE. # IN THE SOFTWARE.
# Build files # Files generated by build
/build /build
/material /material
/site /site
# Files generated by visual tests
/gemini-report
/tests/visual/data
...@@ -25,6 +25,6 @@ CHANGED="$(git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD)" ...@@ -25,6 +25,6 @@ CHANGED="$(git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD)"
# Perform install and prune of NPM dependencies if package.json changed # Perform install and prune of NPM dependencies if package.json changed
if $(echo "$CHANGED" | grep --quiet package.json); then if $(echo "$CHANGED" | grep --quiet package.json); then
echo "Hook[post-merge]: Updating dependencies..." echo "Hook[post-merge]: Updating dependencies"
npm install && npm prune npm install && npm prune
fi fi
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
# Determine current branch # Determine current branch
BRANCH=$(git rev-parse --abbrev-ref HEAD) BRANCH=$(git rev-parse --abbrev-ref HEAD)
echo "Hook[pre-commit]: Checking branch..." echo "Hook[pre-commit]: Checking branch"
# If we're on master, abort commit # If we're on master, abort commit
if [[ "$BRANCH" == "master" ]]; then if [[ "$BRANCH" == "master" ]]; then
......
...@@ -46,6 +46,6 @@ FILES=$(git diff --cached --name-only --diff-filter=ACMR | \ ...@@ -46,6 +46,6 @@ FILES=$(git diff --cached --name-only --diff-filter=ACMR | \
# Run the check and print indicator # Run the check and print indicator
if [ "$FILES" ]; then if [ "$FILES" ]; then
echo "Hook[pre-commit]: Running linter..." echo "Hook[pre-commit]: Running linter"
npm run lint --silent || exit 1 npm run lint --silent || exit 1
fi fi
...@@ -23,14 +23,19 @@ ...@@ -23,14 +23,19 @@
# NPM-related # NPM-related
/node_modules /node_modules
/npm-debug.log /npm-debug.log*
# Build files # Files generated by build
/build /build
/manifest.json /manifest.json
/MANIFEST /MANIFEST
/site /site
# Files generated by visual tests
/gemini-report
/tests/visual/baseline/local
/tests/visual/data
# Distribution files # Distribution files
/dist /dist
/mkdocs_material.egg-info /mkdocs_material.egg-info
...@@ -27,6 +27,29 @@ node_js: ...@@ -27,6 +27,29 @@ node_js:
- 6 - 6
- 7 - 7
# Build visual tests separately
matrix:
include:
- node_js: 5
addons:
artifacts:
paths:
- gemini-report
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- gcc-4.8
- g++-4.8
env:
- CXX=g++-4.8
install: yarn install
script: yarn run test:visual:run
# Limit clone depth to 5, to speed up build
git:
depth: 5
# Cache dependencies # Cache dependencies
cache: cache:
pip: true pip: true
...@@ -34,9 +57,14 @@ cache: ...@@ -34,9 +57,14 @@ cache:
directories: directories:
- node_modules - node_modules
# Install yarn as Travis doesn't support it out of the box
before_install: npm install -g yarn
# Do not install optional dependencies by default
install: yarn install --ignore-optional
# Install dependencies # Install dependencies
before_script: before_script: pip install --user -r requirements.txt
- pip install --user -r requirements.txt
# Perform build and tests # Perform build and tests
script: yarn run build script: yarn run build
...@@ -35,14 +35,17 @@ const config = { ...@@ -35,14 +35,17 @@ const config = {
src: "src/assets", /* Source directory for assets */ src: "src/assets", /* Source directory for assets */
build: "material/assets" /* Target directory for assets */ build: "material/assets" /* Target directory for assets */
}, },
lib: "lib", /* Libraries */ lib: "lib", /* Libraries and tasks */
tests: {
visual: "tests/visual" /* Base directory for visual tests */
},
views: { views: {
src: "src", /* Source directory for views */ src: "src", /* Source directory for views */
build: "material" /* Target directory for views */ build: "material" /* Target directory for views */
} }
} }
const args = yargs let args = yargs
.default("clean", false) /* Clean before build */ .default("clean", false) /* Clean before build */
.default("karma", true) /* Karma watchdog */ .default("karma", true) /* Karma watchdog */
.default("lint", true) /* Lint sources */ .default("lint", true) /* Lint sources */
...@@ -52,6 +55,12 @@ const args = yargs ...@@ -52,6 +55,12 @@ const args = yargs
.default("sourcemaps", false) /* Create sourcemaps */ .default("sourcemaps", false) /* Create sourcemaps */
.argv .argv
/* Only use the last value seen, so overrides are possible */
args = Object.keys(args).reduce((result, arg) => {
result[arg] = [].concat(args[arg]).pop()
return result
}, {})
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Overrides and helpers * Overrides and helpers
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
...@@ -92,7 +101,10 @@ gulp.src = (...glob) => { ...@@ -92,7 +101,10 @@ gulp.src = (...glob) => {
* Helper function to load a task * Helper function to load a task
*/ */
const load = task => { const load = task => {
return require(`./${config.lib}/tasks/${task}`)(gulp, config, args) return done => {
return require(`./${config.lib}/tasks/${task}`)
.call(gulp, gulp, config, args)(done)
}
} }
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
...@@ -228,12 +240,11 @@ gulp.task("assets:clean", [ ...@@ -228,12 +240,11 @@ gulp.task("assets:clean", [
* Minify views * Minify views
*/ */
gulp.task("views:build", (args.revision ? [ gulp.task("views:build", (args.revision ? [
"assets:images:build", "assets:build"
"assets:stylesheets:build",
"assets:javascripts:build"
] : []).concat(args.clean ? [ ] : []).concat(args.clean ? [
"views:clean" "views:clean"
] : []), load("views/build")) ] : []),
load("views/build"))
/* /*
* Clean views * Clean views
...@@ -267,14 +278,45 @@ gulp.task("mkdocs:serve", ...@@ -267,14 +278,45 @@ gulp.task("mkdocs:serve",
load("mkdocs/serve")) load("mkdocs/serve"))
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Tests * Visual tests
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
/* /*
* Start karma test runner * Generate visual tests
*/
gulp.task("tests:visual:generate", [
].concat(args.clean ? [
"tests:visual:clean",
"assets:build",
"views:build"
] : []),
load("tests/visual/generate"))
/*
* Run visual tests
*/
gulp.task("tests:visual:run", [
"tests:visual:generate"
], load("tests/visual/run"))
/*
* Update reference images for visual tests
*/
gulp.task("tests:visual:update",
load("tests/visual/update"))
/*
* Clean files generated by visual tests
*/
gulp.task("tests:visual:clean",
load("tests/visual/clean"))
/*
* Open a SauceConnect session for manual testing
*/ */
gulp.task("tests:unit:watch", gulp.task("tests:visual:session", [
load("tests/unit/watch")) "tests:visual:generate"
], load("tests/visual/session"))
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Interface * Interface
...@@ -286,9 +328,9 @@ gulp.task("tests:unit:watch", ...@@ -286,9 +328,9 @@ gulp.task("tests:unit:watch",
gulp.task("build", [ gulp.task("build", [
"assets:build", "assets:build",
"views:build" "views:build"
].concat(args.mkdocs ].concat(args.mkdocs ? [
? "mkdocs:build" "mkdocs:build"
: [])) ] : []))
/* /*
* Clean assets and documentation * Clean assets and documentation
......
{ {
"rules": { "rules": {
"no-invalid-this": 0 "no-invalid-this": 0,
"max-params": 0
} }
} }
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
*/ */
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Definition * Module
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
export default /* JSX */ { export default /* JSX */ {
......
/*
* Copyright (c) 2016-2017 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
import ecstatic from "ecstatic"
import * as http from "http"
/* ----------------------------------------------------------------------------
* Locals
* ------------------------------------------------------------------------- */
/* Static file server */
let server = null
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Start static file server
*
* @param {string} directory - Directory to serve
* @param {number} port - Port to listen on
* @param {Function} done - Resolve callback
*/
export const start = (directory, port, done) => {
server = http.createServer(ecstatic({
root: directory
}))
/* Listen and register signal handlers */
server.listen(port, "127.0.0.1", done)
for (const signal of ["SIGTERM", "SIGINT", "exit"])
process.on(signal, stop)
}
/**
* Stop static file server
*
* @param {Function} done - Resolve callback
*/
export const stop = done => {
if (server) {
server.close(done)
server = null
}
}
/*
* Copyright (c) 2016-2017 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
import launcher from "sauce-connect-launcher"
/* ----------------------------------------------------------------------------
* Locals
* ------------------------------------------------------------------------- */
/* SauceConnect process */
let server = null
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Open SauceConnect tunnel
*
* @param {string} id - Unique identifier
* @param {string} username - SauceConnect username
* @param {string} accesskey - SauceConnect accesskey
* @param {Function} done - Resolve callback
*/
export const start = (id, username, accesskey, done) => {
launcher({
username,
accessKey: accesskey,
tunnelIdentifier: id
}, (err, proc) => {
if (err)
throw new Error(err)
server = proc
done()
})
/* Register signal handlers */
for (const signal of ["SIGTERM", "SIGINT", "exit"])
process.on(signal, stop)
}
/**
* Close SauceConnect tunnel
*
* @param {Function} done - Resolve callback
*/
export const stop = done => {
if (server) {
server.close(done)
server = null
}
}
...@@ -33,17 +33,31 @@ let server = null ...@@ -33,17 +33,31 @@ let server = null
* Definition * Definition
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
/**
* Start Selenium
*
* @param {Function} done - Resolve callback
*/
export const start = done => { export const start = done => {
selenium.start({}, (err, proc) => { selenium.start({}, (err, proc) => {
/* Register signal handlers */
for (const signal of ["SIGTERM", "SIGINT", "exit"])
process.on(signal, stop)
if (err) { if (err) {
/* Install selenium, if not present */ /* Install selenium, if not present */
if (/^Missing(.*)chromedriver$/.test(err.message)) { if (/^Missing(.*)chromedriver$/.test(err.message)) {
selenium.install(done) new Promise(resolve => {
selenium.install({}, resolve)
})
/* Start selenium again */ /* Start selenium again */
.then(() => {
selenium.start({}, (err_, proc_) => { selenium.start({}, (err_, proc_) => {
server = proc_ server = proc_
done()
})
}) })
/* Otherwise, throw error */ /* Otherwise, throw error */
...@@ -53,20 +67,21 @@ export const start = done => { ...@@ -53,20 +67,21 @@ export const start = done => {
} }
/* Remember process handle */ /* Remember process handle */
server = server || proc server = proc
done() done()
}) })
} }
export const stop = () => { /**
if (server) * Stop Selenium
*
* @param {Function} done - Resolve callback
*/
export const stop = done => {
if (server) {
if (typeof done === "function")
server.on("exit", done)
server.kill() server.kill()
server = null
}
} }
/* ----------------------------------------------------------------------------
* Signal handler
* ------------------------------------------------------------------------- */
/* Register signal handler for all relevant events */
for (const signal of ["SIGTERM", "SIGINT", "exit"])
process.on(signal, stop)
...@@ -101,7 +101,7 @@ export default (gulp, config, args) => { ...@@ -101,7 +101,7 @@ export default (gulp, config, args) => {
}, },
/* Sourcemap support */ /* Sourcemap support */
devtool: args.sourcemaps ? "source-map" : "" devtool: args.sourcemaps ? "inline-source-map" : ""
})) }))
/* Revisioning */ /* Revisioning */
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
import path from "path" import path from "path"
import through from "through2" import through from "through2"
import util from "gulp-util" import util from "gulp-util"
import { CLIEngine } from "eslint" import { CLIEngine } from "eslint"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
......
...@@ -25,6 +25,7 @@ import gulpif from "gulp-if" ...@@ -25,6 +25,7 @@ import gulpif from "gulp-if"
import mincss from "gulp-cssnano" import mincss from "gulp-cssnano"
import mqpacker from "css-mqpacker" import mqpacker from "css-mqpacker"
import postcss from "gulp-postcss" import postcss from "gulp-postcss"
import pseudoclasses from "postcss-pseudo-classes"
import rev from "gulp-rev" import rev from "gulp-rev"
import sass from "gulp-sass" import sass from "gulp-sass"
import sourcemaps from "gulp-sourcemaps" import sourcemaps from "gulp-sourcemaps"
...@@ -54,7 +55,11 @@ export default (gulp, config, args) => { ...@@ -54,7 +55,11 @@ export default (gulp, config, args) => {
postcss([ postcss([
autoprefixer(), autoprefixer(),
mqpacker mqpacker
])) ].concat(!args.optimize ? [
pseudoclasses({
"restrictTo": ["hover", "focus"]
})
] : [])))
/* Minify sources */ /* Minify sources */
.pipe(gulpif(args.optimize, mincss())) .pipe(gulpif(args.optimize, mincss()))
...@@ -63,7 +68,7 @@ export default (gulp, config, args) => { ...@@ -63,7 +68,7 @@ export default (gulp, config, args) => {
.pipe(gulpif(args.revision, rev())) .pipe(gulpif(args.revision, rev()))
.pipe(gulpif(args.revision, .pipe(gulpif(args.revision,
version({ manifest: gulp.src("manifest.json") }))) version({ manifest: gulp.src("manifest.json") })))
.pipe(gulpif(args.sourcemaps, sourcemaps.write("."))) .pipe(gulpif(args.sourcemaps, sourcemaps.write()))
.pipe(gulp.dest(`${config.assets.build}/stylesheets`)) .pipe(gulp.dest(`${config.assets.build}/stylesheets`))
.pipe(gulpif(args.revision, .pipe(gulpif(args.revision,
rev.manifest("manifest.json", { rev.manifest("manifest.json", {
......
...@@ -39,7 +39,7 @@ export default () => { ...@@ -39,7 +39,7 @@ export default () => {
server.kill() server.kill()
/* Spawn MkDocs server */ /* Spawn MkDocs server */
server = child.spawn("mkdocs", ["serve", "-a", "0.0.0.0:8000"], { server = child.spawn("mkdocs", ["serve", "--dev-addr", "0.0.0.0:8000"], {
stdio: "inherit" stdio: "inherit"
}) })
} }
......
/*
* Copyright (c) 2016-2017 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
import clean from "del"
import vinyl from "vinyl-paths"
/* ----------------------------------------------------------------------------
* Task: clean files generated by visual tests
* ------------------------------------------------------------------------- */
export default (gulp, config) => {
return () => {
return gulp.src([
`${config.tests.visual}/data`,
"./gemini-report"
])
.pipe(vinyl(clean))
}
}
/*
* Copyright (c) 2016-2017 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
import child from "child_process"
import path from "path"
import through from "through2"
import util from "gulp-util"
/* ----------------------------------------------------------------------------
* Task: generate visual tests
* ------------------------------------------------------------------------- */
export default (gulp, config) => {
const theme = path.resolve(process.cwd(), config.views.build)
return () => {
return gulp.src(`${config.tests.visual}/suites/**/mkdocs.yml`)
.pipe(
through.obj(function(file, enc, done) {
if (file.isNull() || file.isStream())
return done()
/* Resolve test name and destination */
const name = path.relative(`${config.tests.visual}/suites`,
path.dirname(file.path))
const site = path.resolve(process.cwd(),
`${config.tests.visual}/data`, name, "_")
/* Generate test fixtures with freshly built theme */
const proc = child.spawnSync("mkdocs", [
"build", "--site-dir", site, "--theme-dir", theme
], {
cwd: path.dirname(file.path)
})
/* Emit error, if any */
if (proc.status)
this.emit("error", new util.PluginError("mkdocs",
`Terminated with errors: ${proc.stderr.toString()}`))
/* Terminate */
done()
}))
}
}
/*
* Copyright (c) 2016-2017 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
import moniker from "moniker"
import path from "path"
import * as ecstatic from "~/lib/servers/ecstatic"
import * as sauce from "~/lib/servers/sauce-connect"
import * as selenium from "~/lib/servers/selenium"
import Gemini from "gemini"
/* ----------------------------------------------------------------------------
* Task: run visual tests
* ------------------------------------------------------------------------- */
export default (gulp, config, args) => {
const id = process.env.TRAVIS
? `Travis #${process.env.TRAVIS_BUILD_NUMBER}`
: `Local #${moniker.choose()}`
return done => {
/* Start static file server */
let error = false
new Promise(resolve => {
ecstatic.start(`${config.tests.visual}/data`, 8000, resolve)
/* Create and start test runner */
}).then(() => {
return new Promise((resolve, reject) => {
/* Start SauceConnect tunnel */
if (process.env.CI || process.env.SAUCE) {
if (!process.env.SAUCE_USERNAME ||
!process.env.SAUCE_ACCESS_KEY)
throw new Error(
"SauceConnect: please provide SAUCE_USERNAME " +
"and SAUCE_ACCESS_KEY")
/* Start tunnel, if credentials are given */
sauce.start(
id,
process.env.SAUCE_USERNAME,
process.env.SAUCE_ACCESS_KEY,
err => {
return err ? reject(err) : resolve(sauce)
})
/* Start Selenium */
} else {
selenium.start(() => resolve(selenium))
}
})
/* Setup and run Gemini */
.then(runner => {
const gemini = require(
path.join(process.cwd(), `${config.tests.visual}/config`,
process.env.CI || process.env.SAUCE
? "gemini.sauce-connect.json"
: "gemini.selenium.json"))
/* Add dynamic configuration to capabilities */
for (const key of Object.keys(gemini.browsers)) {
const caps = gemini.browsers[key].desiredCapabilities
caps.tunnelIdentifier = id
caps.public = "private"
caps.name = id
/* Adjust configuration for Travis CI */
if (process.env.CI && process.env.TRAVIS)
caps.public = "public"
}
/* Start Gemini and return runner upon finish */
return new Gemini(gemini).test(`${config.tests.visual}/suites`, {
reporters: ["flat", "html"],
browsers: args.browsers ? [].concat(args.browsers) : null
})
/* Return runner for graceful stop */
.then(status => {
error = status.failed + status.errored > 0
return runner
})
})
/* Stop test runner */
.then(runner => {
return new Promise(resolve => {
runner.stop(resolve)
})
})
/* Stop static file server */
})
.then(() => {
ecstatic.stop(() => {
return error
? done(new Error("Gemini terminated with errors"))
: done()
})
}, err => {
return done(new Error(err))
})
}
}
/*
* Copyright (c) 2016-2017 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
import moniker from "moniker"
import * as ecstatic from "~/lib/servers/ecstatic"
import * as sauce from "~/lib/servers/sauce-connect"
/* ----------------------------------------------------------------------------
* Task: run visual tests
* ------------------------------------------------------------------------- */
export default (gulp, config) => {
return done => {
/* Start static file server */
new Promise(resolve => {
ecstatic.start(`${config.tests.visual}/data`, 8000, resolve)
/* Open SauceConnect tunnel */
}).then(() => {
return new Promise((resolve, reject) => {
if (!process.env.SAUCE_USERNAME ||
!process.env.SAUCE_ACCESS_KEY)
throw new Error(
"SauceConnect: please provide SAUCE_USERNAME " +
"and SAUCE_ACCESS_KEY")
/* Open tunnel */
sauce.start(
`Local #${moniker.choose()}`,
process.env.SAUCE_USERNAME,
process.env.SAUCE_ACCESS_KEY,
err => {
return err ? reject(err) : resolve(sauce)
})
})
/* Close tunnel on CTRL-C */
.then(() => {
return new Promise(resolve => {
process.on("SIGINT", () => {
sauce.stop(resolve)
})
})
})
/* Stop static file server */
})
.then(() => {
ecstatic.stop(done)
}, err => {
return done(err)
})
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment