Merge pull request #723 from mathquill/ci.cleanup
Major changes:
- throughout circle.yml, use the `json` cli tool
- fix the Sauce Connect retry-ing to only retry on failure rather than
always
- on Sauce, use human-readable build name similar to on Circle; in
particular, mention the CircleCI build number
- when applicable keep around pieces of screenshots in `imgs/pieces/`,
and file away the baseline imgs (formerly the `PREV_*` imgs) in
`imgs/baseline/`
- screenshot filename mentions version of browser used (which changes
over time for evergreen browsers)
- `script/screenshots.js` now uses the "promise chain"-style of `wd`,
much fewer callbacks
Also lots of minor improvements to comments, screenshots script error
logging, test names on Sauce, etc.
diff --git a/circle.yml b/circle.yml
index f93227b..e05aea8 100644
--- a/circle.yml
+++ b/circle.yml
@@ -17,8 +17,8 @@
# + https://circleci.com/docs/configuration/
# - our `circle.yml` first installs and runs a tunnel to Sauce Labs
# - and runs `make server`
-# - then it calls out to Sauce Labs' REST API to open a browser that reaches
-# back through the tunnel to access the unit test page on the local server
+# - then it calls out to Sauce Labs' REST API to open browsers that reach
+# back through the tunnel to access test pages on the local server
# + > Sauce Connect allows you to run a test server within the CircleCI
# > build container and expose it it (using a URL like `localhost:8080`)
# > to Sauce Labs’ browsers.
@@ -35,167 +35,195 @@
cache_directories:
- ~/sauce-connect
pre:
- - "test $SAUCE_USERNAME && test $SAUCE_ACCESS_KEY
- # Sauce Labs credentials required. Sign up here: https://saucelabs.com/opensauce/"
- ? |-
- {
- mkdir -p ~/sauce-connect
- cd ~/sauce-connect
- if [ -x sc-*-linux/bin/sc ]; then
- echo Using cached sc-*-linux/bin/sc
- else
- time wget https://saucelabs.com/downloads/sc-latest-linux.tar.gz
- time tar -xzf sc-latest-linux.tar.gz
- fi
- # Sauce Connect randomly fails so try twice
- time sc-*-linux/bin/sc --user $SAUCE_USERNAME --api-key $SAUCE_ACCESS_KEY --readyfile ~/sauce_is_ready \
- || time sc-*-linux/bin/sc --user $SAUCE_USERNAME --api-key $SAUCE_ACCESS_KEY --readyfile ~/sauce_is_ready \
- || echo ERROR > ~/sauce_is_ready
- } >$CIRCLE_ARTIFACTS/sauce-connect.log 2>&1
+ # SauceConnect: download if not cached, and launch with retry
+ test $SAUCE_USERNAME && test $SAUCE_ACCESS_KEY || {
+ echo 'Sauce Labs credentials required. Sign up here: https://saucelabs.com/opensauce/'
+ exit 1
+ }
+
+ mkdir -p ~/sauce-connect
+ cd ~/sauce-connect
+ if [ -x sc-*-linux/bin/sc ]; then
+ echo Using cached sc-*-linux/bin/sc
+ else
+ time wget https://saucelabs.com/downloads/sc-latest-linux.tar.gz
+ time tar -xzf sc-latest-linux.tar.gz
+ fi
+ # Sauce Connect randomly fails so try twice https://git.io/vPN8v
+ time sc-*-linux/bin/sc --user $SAUCE_USERNAME --api-key $SAUCE_ACCESS_KEY \
+ --readyfile ~/sauce_is_ready
+ test -e ~/sauce_was_ready && exit
+ time sc-*-linux/bin/sc --user $SAUCE_USERNAME --api-key $SAUCE_ACCESS_KEY \
+ --readyfile ~/sauce_is_ready
+ test -e ~/sauce_was_ready && exit
+ echo 'ERROR: Exited twice without creating readyfile' \
+ | tee /dev/stderr > ~/sauce_is_ready
+ exit 1
:
background: true
test:
- override:
- # Sauce can connect to Safari on ports 3000, 4000, 7000, and 8000. Edge needs port 7000 or 8000.
+ pre:
+ # Safari on Sauce can only connect to port 3000, 4000, 7000, or 8000. Edge needs port 7000 or 8000.
# https://david263a.wordpress.com/2015/04/18/fixing-safari-cant-connect-to-localhost-issue-when-using-sauce-labs-connect-tunnel/
# https://support.saucelabs.com/customer/portal/questions/14368823-requests-to-localhost-on-microsoft-edge-are-failing-over-sauce-connect
- - PORT=8000 make server >$CIRCLE_ARTIFACTS/make_server.log 2>&1:
+ - PORT=8000 make server:
background: true
- # CircleCI expects test results to be reported in an JUnit/xUnit-style XML
- # file:
- # https://circleci.com/docs/test-metadata/
- # Our unit tests are in a browser, so they can't write to a file, and Sauce
- # apparently truncates custom data in their test result reports, so instead
- # we POST to this trivial Node server on localhost:9000 that writes the
- # body of any POST request to $CIRCLE_TEST_REPORTS/mocha/xunit.xml
- - mkdir -p $CIRCLE_TEST_REPORTS/mocha
+ # Wait for tunnel to be ready (`make server` is much faster, no need to wait for it)
+ - while [ ! -e ~/sauce_is_ready ]; do sleep 1; done; touch ~/sauce_was_ready; test -z "$(<~/sauce_is_ready)"
+
+ override:
- ? |-
- node << 'EOF' >$CIRCLE_TEST_REPORTS/mocha/xunit.xml \
- 2>$CIRCLE_ARTIFACTS/mocha-test-report.log
- require('http').createServer(function(req, res) {
- res.setHeader('Access-Control-Allow-Origin', '*');
+ # Screenshots: capture in the background while running unit tests
+ mkdir -p $CIRCLE_TEST_REPORTS/mocha
+
+ # CircleCI expects test results to be reported in an JUnit/xUnit-style XML file:
+ # https://circleci.com/docs/test-metadata/#a-namemochajsamocha-for-nodejs
+ # Our unit tests are in a browser, so they can't write to a file, and Sauce
+ # apparently truncates custom data in their test result reports, so instead we
+ # POST to this trivial Node server on localhost:9000 that writes the body of
+ # any POST request to $CIRCLE_TEST_REPORTS/junit/test-results.xml
+ node -e '
+ require("http").createServer(function(req, res) {
+ res.setHeader("Access-Control-Allow-Origin", "*");
req.pipe(process.stdout);
- req.on('end', res.end.bind(res));
+ req.on("end", res.end.bind(res));
})
.listen(9000);
- console.error('listening on http://0.0.0.0:9000/');
- EOF
- :
- background: true
+ console.error("listening on http://0.0.0.0:9000/");
+ ' 2>&1 >$CIRCLE_TEST_REPORTS/junit/test-results.xml | {
+ # ^ note: `2>&1` must precede `>$CIRCLE_TEST_REPORTS/...` because
+ # shell redirect is like assignment; if it came after, then both
+ # stdout and stderr would be written to `xunit.xml` and nothing
+ # would be piped into here
- # Wait for tunnel to be ready (`make server` and the trivial Node server
- # are much faster, no need to wait for them)
- - while [ ! -e ~/sauce_is_ready ]; do sleep 1; done; test "$(<~/sauce_is_ready)" != ERROR
+ head -1 # wait for "listening on ..." to be logged
- # Start taking screenshots in the background while the unit tests are running
- - ? |-
- {
+ # https://circleci.com/docs/environment-variables/
+ build_name="CircleCI build #$CIRCLE_BUILD_NUM"
+ if [ $CIRCLE_PR_NUMBER ]; then
+ build_name="$build_name: PR #$CIRCLE_PR_NUMBER"
+ [ "$CIRCLE_BRANCH" ] && build_name="$build_name ($CIRCLE_BRANCH)"
+ else
+ build_name="$build_name: $CIRCLE_BRANCH"
+ fi
+ build_name="$build_name @ ${CIRCLE_SHA1:0:7}"
+ export MQ_CI_BUILD_NAME="$build_name"
+
time { test -d node_modules/wd || npm install wd; }
time node script/screenshots.js http://localhost:8000/test/visual.html \
- && touch ~/screenshots_are_ready || echo ERROR > ~/screenshots_are_ready:
- } >$CIRCLE_ARTIFACTS/screenshots.log 2>&1
+ && touch ~/screenshots_are_ready || echo EXIT STATUS $? | tee /dev/stderr > ~/screenshots_are_ready:
+ }
:
background: true
- # Run in-browser unit tests, based on:
- # https://wiki.saucelabs.com/display/DOCS/JavaScript+Unit+Testing+Methods
- # "build" and "tag" parameters from:
- # https://wiki.saucelabs.com/display/DOCS/Test+Configuration+Options#TestConfigurationOptions-TestAnnotation
- |-
- curl -i https://saucelabs.com/rest/v1/$SAUCE_USERNAME/js-tests \
- -X POST \
+ # Unit tests in the browser
+
+ echo '1. Launch tests'
+ echo
+
+ # https://circleci.com/docs/environment-variables/
+ build_name="CircleCI build #$CIRCLE_BUILD_NUM"
+ if [ $CIRCLE_PR_NUMBER ]; then
+ build_name="$build_name: PR #$CIRCLE_PR_NUMBER"
+ [ "$CIRCLE_BRANCH" ] && build_name="$build_name ($CIRCLE_BRANCH)"
+ else
+ build_name="$build_name: $CIRCLE_BRANCH"
+ fi
+ build_name="$build_name @ ${CIRCLE_SHA1:0:7}"
+
+ # "build" and "customData" parameters from:
+ # https://wiki.saucelabs.com/display/DOCS/Test+Configuration+Options#TestConfigurationOptions-TestAnnotation
+ set -o pipefail
+ curl -i -X POST https://saucelabs.com/rest/v1/$SAUCE_USERNAME/js-tests \
-u $SAUCE_USERNAME:$SAUCE_ACCESS_KEY \
-H 'Content-Type: application/json' \
-d '{
- "build": "'$(git rev-parse HEAD)'",
- "tags": [
- "after-v'$(node -p 'require("./package.json").version')'",
- "circle-ci"
- ],
+ "name": "Unit tests, Mocha",
+ "build": "'"$build_name"'",
+ "customData": {"build_url": "'"$CIRCLE_BUILD_URL"'"},
"framework": "mocha",
"url": "http://localhost:8000/test/unit.html?post_xunit_to=http://localhost:9000",
"platforms": [["", "Chrome", ""]]
- }' | tee js-tests
+ }' | tee /dev/stderr | tail -1 > js-tests.json
- # Wait for tests to finish:
- #
- # > Make the request multiple times as the tests run until the response
- # > contains `completed: true` to the get the final results.
- #
- # https://wiki.saucelabs.com/display/DOCS/JavaScript+Unit+Testing+Methods
- - |-
+ echo '2. Wait for tests to finish:'
+ echo
+ # > Make the request multiple times as the tests run until the response
+ # > contains `completed: true` to the get the final results.
+ # https://wiki.saucelabs.com/display/DOCS/JavaScript+Unit+Testing+Methods
while true # Bash has no do...while >:(
do
sleep 5
- curl -i https://saucelabs.com/rest/v1/$SAUCE_USERNAME/js-tests/status \
- -X POST \
+ curl -i -X POST https://saucelabs.com/rest/v1/$SAUCE_USERNAME/js-tests/status \
-u $SAUCE_USERNAME:$SAUCE_ACCESS_KEY \
-H 'Content-Type: application/json' \
- -d "$(tail -1 js-tests)" \
- | tee status
- tail -1 status > status.json
+ -d @js-tests.json \
+ | tee /dev/stderr | tail -1 > status.json
+
# deliberately do `... != false` rather than `... == true`
# because unexpected values should break rather than infinite loop
- [ "$(node -p 'require("./status.json").completed')" != false ] && break
+ [ "$(jq .completed <status.json)" != false ] && break
done
- # Wait for screenshots to be ready
- - while [ ! -e ~/screenshots_are_ready ]; do sleep 1; done; test "$(<~/screenshots_are_ready)" != ERROR:
- timeout: 300
+ echo '3. Exit with non-zero status code if any unit tests failed'
+ exit "$(jq '.["js tests"][0].result.failures' <status.json)"
- # Stitch together images
- # TODO: Split this into multiple yaml lines. I (Michael)
- # niavely tried to split this into mutiple yaml lines
- # but was unsucessful in doing do.
- |-
- img_dir=$CIRCLE_ARTIFACTS/imgs/
- for x in $(ls $img_dir)
- do
- convert $img_dir/$x/*.png -append $img_dir/$x.png
+ # Stitch together screenshots and diff against master
+
+ echo '0. Wait for screenshots to be ready'
+ while [ ! -e ~/screenshots_are_ready ]; do sleep 1; done
+ test -z "$(<~/screenshots_are_ready)" || exit 1
+
+ echo '1. Stitch together pieces'
+ for img in $(ls $CIRCLE_ARTIFACTS/imgs/pieces/); do
+ convert $(ls -1 $CIRCLE_ARTIFACTS/imgs/pieces/$img/*.png | sort -n) -append $CIRCLE_ARTIFACTS/imgs/$img.png
done
- # Remove all directories in $CIRCLE_ARTIFACTS/img
- # Currently the pieces aren't kept around. If it's
- # desirable to keep them around, we should use
- # cp -r $dir $CIRCLE_ARTIFACTS/img_pieces
- # The reason the image pieces aren't currently kept
- # around is that it was leading to a problem. Specifically,
- # when we get the previous images, we niavely grab any *.png,
- # including the pieces images. This compounded so that each
- # iteration of a test run would have all of the images from
- # the previous test run plus whichever new images were generated.
- rm -R -- $img_dir/*/
+ echo '2. Download the latest screenshots from master'
+ echo
- # Install utility we need
- npm install -g json
+ artifacts_json="$(curl https://circleci.com/api/v1/project/mathquill/mathquill/latest/artifacts?branch=ci.cleanup)"
+ #when done testing, restore: artifacts_json="$(curl https://circleci.com/api/v1/project/mathquill/mathquill/latest/artifacts?branch=master)"
+ echo
+ echo '/latest/artifacts?branch=master:'
+ echo
+ echo "$artifacts_json"
+ echo
- # Download the latest mathquill artifacts.
- curl $(curl https://circleci.com/api/v1/project/mathquill/mathquill/latest/artifacts \
- | json -a url pretty_path -d '\n\t' \
- | grep '\.png$' \
- | grep -v 'PREV' \
- | sed "s:\$CIRCLE_ARTIFACTS/imgs/:-o $img_dir/PREV_:")
+ mkdir $CIRCLE_ARTIFACTS/imgs/baseline/
+ baseline_imgs="$(echo "$artifacts_json" \
+ | jq -r '.[] | .url + " -o " + .pretty_path' \
+ | grep '\.png$' \
+ | grep -v '_DIFF\.png$' \
+ | grep -vF '/pieces/' \
+ | grep -vF '/baseline/' \
+ | sed "s:\$CIRCLE_ARTIFACTS/imgs/:$CIRCLE_ARTIFACTS/imgs/baseline/:")"
+ echo 'Baseline image URLs and files:'
+ echo
+ echo "$baseline_imgs"
+ echo
- # Generate image diffs.
- cd $img_dir
- for file in $(ls PINNED*); do
- prev=PREV_$file
- metric_diff=$(compare -metric AE -compose src $prev $file raw_diff.png)
- composite -alpha on raw_diff.png $prev DIFF_$file
+ test -z "$baseline_imgs" && { echo 'No baseline images to download'; exit; }
+ curl $baseline_imgs
+ echo
+
+ echo '3. Generate image diffs'
+ echo
+ cd $CIRCLE_ARTIFACTS/imgs/
+ for file in $(ls *.png); do
+ # if evergreen browser, browser version of previous screenshot may not match,
+ # so replace previous browser version with glob
+ baseline="$(echo baseline/$(echo $file | sed 's/[^_]*_(evergreen)/*/; s/OS_X_.*/OS_X_*.png/' | tee /dev/stderr) | tee /dev/stderr)"
+ echo "Number of different pixels from baseline in $file:"
+ compare -metric AE $baseline $file ${file/%.png/_DIFF.png}
+ echo
done
+ true # ignore errors like "image widths or heights differ"
- for file in $(ls EVERGREEN*); do
- prev=$(ls PREV_$file*)
- metric_diff=$(compare -metric AE -compose src $prev $file raw_diff.png)
- composite -alpha on raw_diff.png $prev DIFF_$file
- done
-
- rm raw_diff.png
-
- # finally, complain to Circle CI if there were nonzero test failures
- - |-
- [ "$(node -p 'require("./status.json")["js tests"][0].result.failures')" == 0 ]
post:
- killall --wait sc; true # wait for Sauce Connect to close the tunnel; ignore errors since it's just cleanup
diff --git a/script/screenshots.js b/script/screenshots.js
index a28f035..63d9374 100644
--- a/script/screenshots.js
+++ b/script/screenshots.js
@@ -14,186 +14,193 @@
var wd = require('wd');
var fs = require('fs');
-var username = process.env['SAUCE_USERNAME'];
-var accessKey = process.env['SAUCE_ACCESS_KEY'];
-var baseDir = process.env['CIRCLE_ARTIFACTS'] || '/tmp';
var url = process.argv[2];
-var allImgsDir = baseDir+'/imgs';
-fs.mkdirSync(allImgsDir);
+var username = process.env.SAUCE_USERNAME;
+var accessKey = process.env.SAUCE_ACCESS_KEY;
+var build_name = process.env.MQ_CI_BUILD_NAME;
+var baseDir = process.env.CIRCLE_ARTIFACTS;
+if (!baseDir) {
+ console.error('No $CIRCLE_ARTIFACTS found, for testing do something like `CIRCLE_ARTIFACTS=/tmp script/screenshots.js`');
+ process.exit(1);
+}
+fs.mkdirSync(baseDir+'/imgs');
+fs.mkdirSync(baseDir+'/imgs/pieces');
+fs.mkdirSync(baseDir+'/browser_logs');
-var browserVersions = [
+var browsers = [
{
- 'version': {
- // Expecting IE 8
- 'browserName': 'Internet Explorer',
- 'platform': 'Windows XP'
+ config: {
+ browserName: 'Internet Explorer',
+ platform: 'Windows XP'
},
- 'pinned': 'PINNED'
+ pinned: true // assume pinned to IE 8
},
{
- 'version': {
- // Expecting IE 11
- 'browserName': 'Internet Explorer',
- 'platform': 'Windows 7'
+ config: {
+ browserName: 'Internet Explorer',
+ platform: 'Windows 7'
},
- 'pinned': 'PINNED'
+ pinned: true // assume pinned to IE 11
},
{
- 'version': {
- 'browserName': 'MicrosoftEdge',
- 'platform': 'Windows 10'
- },
- 'pinned': 'EVERGREEN'
+ config: {
+ browserName: 'MicrosoftEdge',
+ platform: 'Windows 10'
+ }
},
{
- 'version': {
- 'browserName': 'Firefox',
- 'platform': 'OS X 10.11'
- },
- 'pinned': 'EVERGREEN'
+ config: {
+ browserName: 'Firefox',
+ platform: 'OS X 10.11'
+ }
},
{
- 'version': {
- 'browserName': 'Safari',
- 'platform': 'OS X 10.11'
- },
- 'pinned': 'EVERGREEN'
+ config: {
+ browserName: 'Safari',
+ platform: 'OS X 10.11'
+ }
},
{
- 'version': {
- 'browserName': 'Chrome',
- 'platform': 'OS X 10.11'
- },
- 'pinned': 'EVERGREEN'
+ config: {
+ browserName: 'Chrome',
+ platform: 'OS X 10.11'
+ }
},
{
- 'version': {
- 'browserName': 'Firefox',
- 'platform': 'Linux'
- },
- 'pinned': 'EVERGREEN'
- },
+ config: {
+ browserName: 'Firefox',
+ platform: 'Linux'
+ }
+ }
];
-browserVersions.forEach(function(obj) {
- var cfg = obj.version;
- var browserDriver = wd.remote('ondemand.saucelabs.com', 80, username, accessKey);
- // The following is in the style of
- // https://github.com/admc/wd/blob/62f2b0060d36a402de5634477b26a5ed4c051967/examples/async/chrome.js#L25-L40
- browserDriver.init(cfg, function(err, _, capabilities) {
- if (err) console.log(err);
- console.log(cfg.browserName,cfg.platform,'init')
+browsers.forEach(function(browser) {
+ browser.config.build = build_name;
+ browser.config.name = 'Visual tests, ' + browser.config.browserName + ' on ' + browser.config.platform;
+ browser.config.customData = {build_url: process.env.CIRCLE_BUILD_URL};
+ var browserDriver = wd.promiseChainRemote('ondemand.saucelabs.com', 80, username, accessKey);
+ return browserDriver.init(browser.config)
+ .then(function(args) {
+ var cfg = browser.config, capabilities = args[1];
+ var version = capabilities.version || capabilities.browserVersion;
+ var sessionName = [cfg.browserName, version, cfg.platform].join(' ');
+ if (capabilities.platformVersion) sessionName += ' ' + capabilities.platformVersion;
+ console.log(sessionName, 'init', args);
- var browser = cfg.browserName.replace(/\s/g, '_');
- var platform = cfg.platform.replace(/\s/g, '_');
- var piecesDir = allImgsDir+'/'+obj.pinned+'_'+platform+'_'+browser;
- fs.mkdirSync(piecesDir);
+ var evergreen = browser.pinned ? '' : '_(evergreen)';
+ var fileName = [cfg.browserName, version + evergreen, cfg.platform].join('_');
+ if (capabilities.platformVersion) fileName += ' ' + capabilities.platformVersion;
+ fileName = fileName.replace(/ /g, '_');
- browserDriver.get(url, function(err) {
- if (err) console.log(err);
- console.log(cfg.browserName,cfg.platform,'get')
- browserDriver.safeExecute('document.documentElement.scrollHeight', function(err,scrollHeight) {
- if (err) console.log(err);
- console.log(cfg.browserName,cfg.platform,'get scrollHeight')
- browserDriver.safeExecute('document.documentElement.clientHeight', function(err,viewportHeight) {
- if (err) console.log(err);
- console.log(cfg.browserName,cfg.platform,'get clientHeight')
+ return browserDriver.get(url)
+ .then(willLog(sessionName, 'get'))
+ .safeExecute('document.body.focus()') // blur anything that's auto-focused
+ .then(willLog(sessionName, 'document.body.focus()'))
+ .safeExecute('document.documentElement.style.overflow = "hidden"') // hide scrollbars
+ .then(willLog(sessionName, 'hide scrollbars'))
+ .then(function() {
+ // Microsoft Edge starts out with illegally big window: https://git.io/vD63O
+ if (cfg.browserName === 'MicrosoftEdge') {
+ return browserDriver.getWindowSize()
+ .then(function(size) {
+ return browserDriver.setWindowSize(size.width, size.height)
+ })
+ .then(willLog(sessionName, 'reset window size (Edge-only workaround)'))
+ }
+ })
+ .then(function() {
+ return [browserDriver.safeExecute('document.documentElement.scrollHeight'),
+ browserDriver.safeExecute('document.documentElement.clientHeight')];
+ })
+ .spread(function(scrollHeight, viewportHeight) {
+ console.log(sessionName, 'get scrollHeight, clientHeight', scrollHeight, viewportHeight);
- // Firefox and Internet Explorer will take a screenshot of the entire webpage,
- if (cfg.browserName != 'Safari' && cfg.browserName != 'Chrome' && cfg.browserName != 'MicrosoftEdge') {
- // saves file in the file `piecesDir/browser_version_platform/*.png`
- var filename = piecesDir+'/'+browser+'_'+platform+'.png';
- browserDriver.saveScreenshot(filename, function(err) {
- if (err) console.log(err);
- console.log(cfg.browserName,cfg.platform,'saveScreenshot');
+ // the easy case: Firefox and IE return a screenshot of the entire webpage
+ if (cfg.browserName === 'Firefox' || cfg.browserName === 'Internet Explorer') {
+ return browserDriver.saveScreenshot(baseDir + '/imgs/' + fileName + '.png')
+ .then(willLog(sessionName, 'saveScreenshot'))
+ // the hard case: for Chrome, Safari, and Edge, scroll through the page and
+ // take screenshots of each piece; circle.yml will stitch them together
+ } else {
+ var piecesDir = baseDir + '/imgs/pieces/' + fileName + '/';
+ fs.mkdirSync(piecesDir);
- browserDriver.log('browser', function(err,logs) {
- if (err) console.log(err);
- console.log(cfg.browserName,cfg.platform,'log');
+ var scrollTop = 0;
+ var index = 1;
- var logfile = baseDir+'/'+browser+'_'+platform+'.log'
- logs = logs || [];
- fs.writeFile(logfile,logs.join('\n'), function(err) {
- if (err) console.log(err);
+ return (function loop() {
+ return browserDriver.safeEval('window.scrollTo(0,'+scrollTop+');')
+ .then(willLog(sessionName, 'scrollTo()'))
+ .saveScreenshot(piecesDir + index + '.png')
+ .then(function() {
+ console.log(sessionName, 'saveScreenshot');
- browserDriver.quit();
- });
- });
+ scrollTop += viewportHeight;
+ index += 1;
- });
- } else {
- var scrollTop = 0;
-
- // loop generates the images. Firefox and Internet Explorer will take
- // a screenshot of the entire webpage, but Opera, Safari, and Chrome
- // do not. For those browsers we scroll through the page and take
- // incremental screenshots.
- (function loop() {
- var index = (scrollTop/viewportHeight) + 1;
-
- // Use `window.scrollTo` because thats what jQuery does
- // https://github.com/jquery/jquery/blob/1.12.3/src/offset.js#L186
- // `window.scrollTo` was used instead of jQuery because jQuery was
+ // if the viewport hasn't passed the bottom edge of the page yet,
+ // scroll down and take another screenshot
+ if (scrollTop + viewportHeight <= scrollHeight) {
+ // Use `window.scrollTo` because thats what jQuery does:
+ // https://github.com/jquery/jquery/blob/1.12.3/src/offset.js#L186
+ // Use `window.scrollTo` instead of jQuery because jQuery was
// causing a stackoverflow in Safari.
- browserDriver.safeEval('window.scrollTo(0,'+scrollTop+');', function(err) {
- if (err) console.log(JSON.stringify(err));
- console.log(cfg.browserName,cfg.platform,'safeEval 1');
+ return loop();
+ } else { // we are past the bottom edge of the page, reduce window size to
+ // fit only the part of the page that hasn't been screenshotted.
- // saves file in the file `piecesDir/browser_version_platform/#.png`
- var filename = piecesDir+'/'+index+'.png';
- browserDriver.saveScreenshot(filename, function(err) {
- if (err) console.log(err);
- console.log(cfg.browserName,cfg.platform,'saveScreenshot');
+ // If there is no remaining part of the page, we're done, short-circuit
+ if (scrollTop === scrollHeight) return browserDriver;
- scrollTop += viewportHeight;
- if (scrollTop + viewportHeight > scrollHeight) {
- browserDriver.getWindowSize(function(err,size) {
- if (err) console.log(err);
- console.log(cfg.browserName,cfg.platform,'getWindowSize');
- // account for the viewport offset
- var extra = size.height - viewportHeight;
- browserDriver.setWindowSize(size.width, (scrollHeight-scrollTop)+extra, function(err) {
- if (err) console.log(err);
- console.log(cfg.browserName,cfg.platform,'setWindowSize');
-
- browserDriver.safeEval('window.scrollTo(0,'+scrollHeight+');', function(err) {
- if (err) console.log(JSON.stringify(err));
- console.log(cfg.browserName,cfg.platform,'safeEval 2');
-
- index++;
- var filename = piecesDir+'/'+index+'.png';
- browserDriver.saveScreenshot(filename, function(err) {
- if (err) console.log(err);
- console.log(cfg.browserName,cfg.platform,'saveScreenshot Final');
-
- browserDriver.log('browser', function(err,logs) {
- if (err) console.log(err);
- console.log(cfg.browserName,cfg.platform,'log');
-
- var logfile = baseDir+'/'+browser+'_'+platform+'.log'
- logs = logs || [];
- fs.writeFile(logfile,logs.join('\n'), function(err) {
- if (err) console.log(err);
- console.log(cfg.browserName,cfg.platform,'writeFile');
-
- browserDriver.quit();
- });
- });
- });
- });
-
- });
- });
- } else {
- loop();
- }
- });
+ return browserDriver.getWindowSize()
+ .then(function(windowSize) {
+ console.log(sessionName, 'getWindowSize');
+ // window size is a little bigger than the viewport because of address
+ // bar and scrollbars and stuff
+ var windowPadding = windowSize.height - viewportHeight;
+ var newWindowHeight = scrollHeight - scrollTop + windowPadding;
+ return browserDriver.setWindowSize(windowSize.width, newWindowHeight)
+ .then(willLog(sessionName, 'setWindowSize'))
+ .safeEval('window.scrollTo(0,'+scrollHeight+');')
+ .then(willLog(sessionName, 'scrollTo() Final'))
+ .saveScreenshot(piecesDir + index + '.png')
+ .then(willLog(sessionName, 'saveScreenshot Final'));
});
- })();
- }
- });
+ }
+ });
+ }());
+ }
+ })
+ .then(function() {
+ return browserDriver.log('browser')
+ .then(function(logs) {
+ var logfile = baseDir + '/browser_logs/' + sessionName.replace(/ /g, '_') + '.log';
+ return new Promise(function(resolve, reject) {
+ fs.writeFile(logfile, JSON.stringify(logs, null, 2), function(err) {
+ err ? reject(err) : resolve();
+ });
+ })
+ .then(willLog(sessionName, 'writeFile'));
+ }, function(err) {
+ // the Edge, IE, and Firefox-on-macOS drivers don't support logs, but the others do
+ console.log(sessionName, 'Error fetching logs:', JSON.stringify(err, null, 2));
});
});
- });
+ })
+ .sauceJobStatus(true)
+ .fail(function(err) {
+ console.log('ERROR:', browser.config.browserName, browser.config.platform);
+ console.log(JSON.stringify(err, null, 2));
+ return browserDriver.sauceJobStatus(false);
+ })
+ .quit();
+
+ function willLog() {
+ var msg = [].join.call(arguments, ' ');
+ return function(value) {
+ console.log(msg);
+ return value;
+ };
+ }
});