From f0c400003bfadd8a0f2f4499aa78dcdb1ab30138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Og=C3=B3rek?= Date: Tue, 22 May 2018 12:13:42 +0200 Subject: [PATCH 1/5] feat: Async Loader with tests --- karma.sauce.config.js | 117 ----------- karma.unit.config.js | 13 -- karma.config.js => karma/karma.config.js | 7 +- karma/karma.integration-sauce.config.js | 10 + .../karma.integration.config.js | 7 +- karma/karma.loader-sauce.config.js | 10 + karma/karma.loader.config.js | 15 ++ karma/karma.sauce.config.js | 97 +++++++++ karma/karma.unit.config.js | 9 + package-lock.json | 198 +----------------- package.json | 14 +- src/loader.js | 78 +++++++ test/globals.js | 2 - test/integration/loader-test.js | 82 ++++++++ test/integration/loader.html | 47 +++++ test/integration/test.js | 2 +- 16 files changed, 366 insertions(+), 342 deletions(-) delete mode 100644 karma.sauce.config.js delete mode 100644 karma.unit.config.js rename karma.config.js => karma/karma.config.js (91%) create mode 100644 karma/karma.integration-sauce.config.js rename karma.integration.config.js => karma/karma.integration.config.js (86%) create mode 100644 karma/karma.loader-sauce.config.js create mode 100644 karma/karma.loader.config.js create mode 100644 karma/karma.sauce.config.js create mode 100644 karma/karma.unit.config.js create mode 100644 src/loader.js delete mode 100644 test/globals.js create mode 100644 test/integration/loader-test.js create mode 100644 test/integration/loader.html diff --git a/karma.sauce.config.js b/karma.sauce.config.js deleted file mode 100644 index bf363bb359c5..000000000000 --- a/karma.sauce.config.js +++ /dev/null @@ -1,117 +0,0 @@ -var commonConfig = require('./karma.config'); - -var customLaunchers = { - sl_chrome: { - base: 'SauceLabs', - browserName: 'chrome', - platform: 'Windows 10', - version: 'latest' - }, - sl_firefox: { - base: 'SauceLabs', - browserName: 'firefox', - platform: 'Windows 10', - version: 'latest' - }, - sl_edge: { - base: 'SauceLabs', - browserName: 'microsoftedge', - version: 'latest', - platform: 'Windows 10' - }, - sl_ie_11: { - base: 'SauceLabs', - browserName: 'internet explorer', - platform: 'Windows 7', - version: '11' - }, - sl_ie_10: { - base: 'SauceLabs', - browserName: 'internet explorer', - platform: 'Windows 7', - version: '10' - }, - sl_safari: { - base: 'SauceLabs', - browserName: 'safari', - platform: 'OS X 10.12', - version: '11.0' - }, - sl_ios: { - base: 'SauceLabs', - browserName: 'iphone', - platform: 'OS X 10.12', - version: '11.0' - }, - sl_android_7: { - base: 'SauceLabs', - browserName: 'Chrome', - platform: 'Android', - version: '7.1', - device: 'Android GoogleAPI Emulator' - }, - sl_android_6: { - base: 'SauceLabs', - browserName: 'Chrome', - platform: 'Android', - version: '6.0', - device: 'Android Emulator' - }, - sl_android_5: { - base: 'SauceLabs', - browserName: 'android', - platform: 'Linux', - version: '5.1' - }, - sl_android_4: { - base: 'SauceLabs', - browserName: 'android', - platform: 'Linux', - version: '4.4' - } -}; - -var testFiles = [ - {pattern: 'node_modules/es6-promise/dist/es6-promise.auto.js', included: false}, - {pattern: 'node_modules/whatwg-fetch/fetch.js', included: false}, - {pattern: 'test/integration/123', included: false}, - {pattern: 'test/integration/throw-string.js', included: false}, - {pattern: 'test/integration/throw-error.js', included: false}, - {pattern: 'test/integration/throw-object.js', included: false}, - {pattern: 'test/integration/example.json', included: false}, - {pattern: 'test/integration/frame.html', included: false}, - 'test/integration/test.js', - 'test/globals.js', - 'build/raven.js', - 'build/raven.test.js' -]; - -module.exports = function(config) { - var testConfig = Object.assign({}, commonConfig, { - files: testFiles, - logLevel: config.LOG_INFO, - customLaunchers: customLaunchers, - browsers: Object.keys(customLaunchers), - reporters: ['failed', 'saucelabs'], - singleRun: true, - plugins: commonConfig.plugins.concat(['karma-sauce-launcher']), - build: process.env.TRAVIS_BUILD_NUMBER, - // SauceLabs allows for 2 tunnels only, therefore some browsers will have to wait - // rather long time. Plus mobile emulators tend to require a lot of time to start up. - // 10 minutes should be more than enough to run all of them. - browserNoActivityTimeout: 600000, - captureTimeout: 600000, - sauceLabs: { - startConnect: false, - // Just something "random" so we don't have to provide additional ENV var when running locally - tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER || Math.ceil(Math.random() * 1337), - recordScreenshots: false, - recordVideo: false, - testName: - 'Raven.js' + - (process.env.TRAVIS_JOB_NUMBER ? ' #' + process.env.TRAVIS_JOB_NUMBER : ''), - public: 'public' - } - }); - config.set(testConfig); -}; diff --git a/karma.unit.config.js b/karma.unit.config.js deleted file mode 100644 index d6f468af2f3a..000000000000 --- a/karma.unit.config.js +++ /dev/null @@ -1,13 +0,0 @@ -var commonConfig = require('./karma.config'); - -var testFiles = ['test/globals.js', 'build/raven.test.js']; - -module.exports = function(config) { - var testConfig = Object.assign( - {}, - commonConfig, - {files: testFiles}, - {logLevel: config.LOG_INFO} - ); - config.set(testConfig); -}; diff --git a/karma.config.js b/karma/karma.config.js similarity index 91% rename from karma.config.js rename to karma/karma.config.js index f1500ba0e4e3..40cbf4cb7901 100644 --- a/karma.config.js +++ b/karma/karma.config.js @@ -3,7 +3,7 @@ module.exports = { // base path that will be used to resolve all patterns (eg. files, exclude) - basePath: '', + basePath: '../', // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter @@ -58,9 +58,12 @@ module.exports = { } }, + // https://docs.travis-ci.com/user/gui-and-headless-browsers/#Karma-and-Firefox-inactivity-timeouts + browserNoActivityTimeout: 30000, + // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits - singleRun: false, + singleRun: true, // Concurrency level // how many browser should be started simultaneous diff --git a/karma/karma.integration-sauce.config.js b/karma/karma.integration-sauce.config.js new file mode 100644 index 000000000000..19611e51fe6c --- /dev/null +++ b/karma/karma.integration-sauce.config.js @@ -0,0 +1,10 @@ +var commonSauceConfig = require('./karma.sauce.config'); +var files = require('./karma.integration.config').files; + +module.exports = function(config) { + var testConfig = Object.assign({}, commonSauceConfig, { + logLevel: config.LOG_INFO, + files: files.concat(['build/raven.test.js']) + }); + config.set(testConfig); +}; diff --git a/karma.integration.config.js b/karma/karma.integration.config.js similarity index 86% rename from karma.integration.config.js rename to karma/karma.integration.config.js index ee544fbce0ca..1ef104390cdf 100644 --- a/karma.integration.config.js +++ b/karma/karma.integration.config.js @@ -9,12 +9,13 @@ var testFiles = [ {pattern: 'test/integration/throw-object.js', included: false}, {pattern: 'test/integration/example.json', included: false}, {pattern: 'test/integration/frame.html', included: false}, - 'test/integration/test.js', - 'test/globals.js', - 'build/raven.js', + {pattern: 'build/raven.js', included: false}, + 'test/integration/test.js' ]; module.exports = function(config) { var testConfig = Object.assign({}, commonConfig, {files: testFiles}); config.set(testConfig); }; + +module.exports.files = testFiles; diff --git a/karma/karma.loader-sauce.config.js b/karma/karma.loader-sauce.config.js new file mode 100644 index 000000000000..8b8133e7fac9 --- /dev/null +++ b/karma/karma.loader-sauce.config.js @@ -0,0 +1,10 @@ +var commonSauceConfig = require('./karma.sauce.config'); +var files = require('./karma.loader.config').files; + +module.exports = function(config) { + var testConfig = Object.assign({}, commonSauceConfig, { + logLevel: config.LOG_INFO, + files: files + }); + config.set(testConfig); +}; diff --git a/karma/karma.loader.config.js b/karma/karma.loader.config.js new file mode 100644 index 000000000000..735c341b4e98 --- /dev/null +++ b/karma/karma.loader.config.js @@ -0,0 +1,15 @@ +var commonConfig = require('./karma.config'); + +var testFiles = [ + {pattern: 'node_modules/es6-promise/dist/es6-promise.auto.js', included: false}, + {pattern: 'test/integration/loader.html', included: false}, + {pattern: 'build/raven.js', included: false}, + {pattern: 'src/loader.js', included: false}, + 'test/integration/loader-test.js' +]; + +module.exports = function(config) { + var testConfig = Object.assign({}, commonConfig, {files: testFiles}); + config.set(testConfig); +}; +module.exports.files = testFiles; diff --git a/karma/karma.sauce.config.js b/karma/karma.sauce.config.js new file mode 100644 index 000000000000..4ac381fd6f97 --- /dev/null +++ b/karma/karma.sauce.config.js @@ -0,0 +1,97 @@ +var commonConfig = require('./karma.config'); + +var customLaunchers = { + sl_chrome: { + base: 'SauceLabs', + browserName: 'chrome', + platform: 'Windows 10', + version: 'latest' + }, + sl_firefox: { + base: 'SauceLabs', + browserName: 'firefox', + platform: 'Windows 10', + version: 'latest' + }, + sl_edge: { + base: 'SauceLabs', + browserName: 'microsoftedge', + version: 'latest', + platform: 'Windows 10' + }, + sl_ie_11: { + base: 'SauceLabs', + browserName: 'internet explorer', + platform: 'Windows 7', + version: '11' + }, + sl_ie_10: { + base: 'SauceLabs', + browserName: 'internet explorer', + platform: 'Windows 7', + version: '10' + }, + sl_safari: { + base: 'SauceLabs', + browserName: 'safari', + platform: 'OS X 10.12', + version: '11.0' + }, + sl_ios: { + base: 'SauceLabs', + browserName: 'iphone', + platform: 'OS X 10.12', + version: '11.0' + }, + sl_android_7: { + base: 'SauceLabs', + browserName: 'Chrome', + platform: 'Android', + version: '7.1', + device: 'Android GoogleAPI Emulator' + }, + sl_android_6: { + base: 'SauceLabs', + browserName: 'Chrome', + platform: 'Android', + version: '6.0', + device: 'Android Emulator' + }, + sl_android_5: { + base: 'SauceLabs', + browserName: 'android', + platform: 'Linux', + version: '5.1' + }, + sl_android_4: { + base: 'SauceLabs', + browserName: 'android', + platform: 'Linux', + version: '4.4' + } +}; + +module.exports = Object.assign({}, commonConfig, { + customLaunchers: customLaunchers, + browsers: Object.keys(customLaunchers), + reporters: ['failed', 'saucelabs'], + singleRun: true, + plugins: commonConfig.plugins.concat(['karma-sauce-launcher']), + build: process.env.TRAVIS_BUILD_NUMBER, + // SauceLabs allows for 2 tunnels only, therefore some browsers will have to wait + // rather long time. Plus mobile emulators tend to require a lot of time to start up. + // 10 minutes should be more than enough to run all of them. + browserNoActivityTimeout: 600000, + captureTimeout: 600000, + sauceLabs: { + startConnect: false, + // Just something "random" so we don't have to provide additional ENV var when running locally + tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER || Math.ceil(Math.random() * 1337), + recordScreenshots: false, + recordVideo: false, + testName: + 'Raven.js' + + (process.env.TRAVIS_JOB_NUMBER ? ' #' + process.env.TRAVIS_JOB_NUMBER : ''), + public: 'public' + } +}); diff --git a/karma/karma.unit.config.js b/karma/karma.unit.config.js new file mode 100644 index 000000000000..9349c48a1b9f --- /dev/null +++ b/karma/karma.unit.config.js @@ -0,0 +1,9 @@ +var commonConfig = require('./karma.config'); + +module.exports = function(config) { + var testConfig = Object.assign({}, commonConfig, { + logLevel: config.LOG_INFO, + files: ['build/raven.test.js'] + }); + config.set(testConfig); +}; diff --git a/package-lock.json b/package-lock.json index a57b8993306a..6095a95b0b27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "raven-js", - "version": "3.22.3", + "version": "3.25.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -20,16 +20,6 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, - "accepts": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", - "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", - "dev": true, - "requires": { - "mime-types": "2.1.17", - "negotiator": "0.6.1" - } - }, "acorn": { "version": "4.0.13", "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", @@ -494,21 +484,6 @@ "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", "dev": true }, - "basic-auth": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.0.tgz", - "integrity": "sha1-AV2z81PgLlY3d1X5YnQuiYHnu7o=", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, - "batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", - "dev": true - }, "bcrypt-pbkdf": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", @@ -1542,12 +1517,6 @@ } } }, - "connect-livereload": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/connect-livereload/-/connect-livereload-0.5.4.tgz", - "integrity": "sha1-gBV9E3HJ83zBQDmrGJWXDRGdw7w=", - "dev": true - }, "console-browserify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", @@ -1938,12 +1907,6 @@ "minimalistic-assert": "1.0.0" } }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true - }, "detective": { "version": "4.7.1", "resolved": "https://registry.npmjs.org/detective/-/detective-4.7.1.tgz", @@ -2572,12 +2535,6 @@ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "dev": true }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "dev": true - }, "event-emitter": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", @@ -3002,12 +2959,6 @@ "integrity": "sha1-71SRSQ+UM7cF+qdyScmQKa40hVk=", "dev": true }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "dev": true - }, "fs-access": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", @@ -4314,36 +4265,6 @@ "rimraf": "2.6.2" } }, - "grunt-contrib-connect": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/grunt-contrib-connect/-/grunt-contrib-connect-0.11.2.tgz", - "integrity": "sha1-HAoHB9OzKNnPO0tJDrhMSV2Tau0=", - "dev": true, - "requires": { - "async": "0.9.2", - "connect": "3.6.5", - "connect-livereload": "0.5.4", - "morgan": "1.9.0", - "opn": "1.0.2", - "portscanner": "1.2.0", - "serve-index": "1.9.1", - "serve-static": "1.13.1" - }, - "dependencies": { - "async": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", - "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", - "dev": true - }, - "opn": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/opn/-/opn-1.0.2.tgz", - "integrity": "sha1-uQlkM0bQChq8l3qLlvPOPFPVz18=", - "dev": true - } - } - }, "grunt-contrib-copy": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/grunt-contrib-copy/-/grunt-contrib-copy-0.8.2.tgz", @@ -6440,30 +6361,6 @@ "integrity": "sha1-XceVaVZaENbv7VQ5SR5p0jkuWPE=", "dev": true }, - "morgan": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.0.tgz", - "integrity": "sha1-0B+mxlhZt2/PMbPLU6OCGjEdgFE=", - "dev": true, - "requires": { - "basic-auth": "2.0.0", - "debug": "2.6.9", - "depd": "1.1.2", - "on-finished": "2.3.0", - "on-headers": "1.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -6646,12 +6543,6 @@ "ee-first": "1.1.1" } }, - "on-headers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", - "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=", - "dev": true - }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -7094,23 +6985,6 @@ "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", "dev": true }, - "portscanner": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-1.2.0.tgz", - "integrity": "sha1-sUu9olfRTDEPqcwJaCrwLUCWGAI=", - "dev": true, - "requires": { - "async": "1.5.2" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - } - } - }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -7840,76 +7714,6 @@ "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", "dev": true }, - "send": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", - "integrity": "sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==", - "dev": true, - "requires": { - "debug": "2.6.9", - "depd": "1.1.2", - "destroy": "1.0.4", - "encodeurl": "1.0.1", - "escape-html": "1.0.3", - "etag": "1.8.1", - "fresh": "0.5.2", - "http-errors": "1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "2.3.0", - "range-parser": "1.2.0", - "statuses": "1.3.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", - "dev": true, - "requires": { - "accepts": "1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "1.0.3", - "http-errors": "1.6.2", - "mime-types": "2.1.17", - "parseurl": "1.3.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "serve-static": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", - "integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==", - "dev": true, - "requires": { - "encodeurl": "1.0.1", - "escape-html": "1.0.3", - "parseurl": "1.3.2", - "send": "0.16.1" - } - }, "set-immediate-shim": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", diff --git a/package.json b/package.json index 5a9b20ba8ed7..deec78eda095 100644 --- a/package.json +++ b/package.json @@ -19,14 +19,14 @@ "lint": "eslint .", "precommit": "lint-staged", "publish": "grunt publish", - "test": "npm run lint && grunt build.test && npm run test:unit && npm run test:integration && npm run test:typescript", - "test:karma:unit": "karma start karma.unit.config.js", - "test:karma:integration": "karma start karma.integration.config.js ", - "test:karma:sauce": "karma start karma.sauce.config.js ", - "test:unit": "npm run test:karma:unit -- --single-run", - "test:integration": "npm run test:karma:integration -- --single-run", + "test": "npm run lint && grunt build.test && npm run test:unit && npm run test:loader && npm run test:integration && npm run test:typescript", + "test:unit": "karma start karma/karma.unit.config.js", + "test:integration": "karma start karma/karma.integration.config.js", + "test:integration-sauce": "karma start karma/karma.integration-sauce.config.js", + "test:loader": "karma start karma/karma.loader.config.js", + "test:loader-sauce": "karma start karma/karma.loader-sauce.config.js", "test:typescript": "tsc --noEmit --noImplicitAny typescript/raven-tests.ts", - "test:ci": "npm run lint && grunt test:ci && npm run test:karma:sauce", + "test:ci": "npm run lint && grunt test:ci && npm run test:loader-sauce && npm run test:integration-sauce", "test:size": "grunt dist && bundlesize && git checkout -- dist/" }, "devDependencies": { diff --git a/src/loader.js b/src/loader.js new file mode 100644 index 000000000000..ec4ca2823287 --- /dev/null +++ b/src/loader.js @@ -0,0 +1,78 @@ +(function(_window, _document, _script, _onerror, _onunhandledrejection) { + var SENTRY_SDK = _window.SENTRY_SDK; + + // Create a namespace and attach function that will store captured exception + // Because functions are also objects, we can attach the queue itself straight to it and save some bytes + var queue = function(exception) { + queue.data.push(exception); + }; + queue.data = []; + + // Store reference to the old `onerror` handler and override it with our own function + // that will just push exceptions to the queue and call through old handler if we found one + var _oldOnerror = _window[_onerror]; + _window[_onerror] = function(message, source, lineno, colno, exception) { + // Use keys as "data type" to save some characters" + queue({ + e: [].slice.call(arguments) + }); + + if (_oldOnerror) _oldOnerror.apply(_window, arguments); + }; + + // Do the same store/queue/call operations for `onunhandledrejection` event + var _oldOnunhandledrejection = _window[_onunhandledrejection]; + _window[_onunhandledrejection] = function(exception) { + queue({ + p: exception.reason + }); + if (_oldOnunhandledrejection) _oldOnunhandledrejection.apply(_window, arguments); + }; + + // Create a `script` tag with provided SDK `url` and attach it just before the first, already existing `script` tag + // Scripts that are dynamically created and added to the document are async by default, + // they don't block rendering and execute as soon as they download, meaning they could + // come out in the wrong order. Because of that we don't need async=1 as GA does. + // it was probably(?) a legacy behavior that they left to not modify few years old snippet + // https://www.html5rocks.com/en/tutorials/speed/script-loading/ + var _currentScriptTag = _document.getElementsByTagName(_script)[0]; + var _newScriptTag = _document.createElement(_script); + _newScriptTag.src = SENTRY_SDK.url; + _newScriptTag.crossorigin = 'anonymous'; + + // Once our SDK is loaded + _newScriptTag.addEventListener('load', function() { + try { + // Restore onerror/onunhandledrejection handlers + _window[_onerror] = _oldOnerror; + _window[_onunhandledrejection] = _oldOnunhandledrejection; + + var data = queue.data; + var SDK = _window.Raven; + // Configure it using provided DSN and config object + SDK.config(SENTRY_SDK.dsn, SENTRY_SDK.options).install(); + // Because we installed the SDK, at this point we have an access to TraceKit's handler, + // which can take care of browser differences (eg. missing exception argument in onerror) + var tracekitErrorHandler = _window[_onerror]; + + // And capture all previously caught exceptions + if (data.length) { + for (var i = 0; i < data.length; i++) { + if (data[i].e) { + tracekitErrorHandler.apply(SDK.TraceKit, data[i].e); + } else if (data[i].p) { + SDK.captureException(data[i].p); + } + } + } + } catch (o_O) { + console.log(o_O); + } + }); + + // TODO: Figure out how to work around this for tests. We cannot just attach it without the delay + // as raven.js file is pre-loaded and XHR will behave like a synchronous call + setTimeout(function() { + _currentScriptTag.parentNode.insertBefore(_newScriptTag, _currentScriptTag); + }, 500); +})(window, document, 'script', 'onerror', 'onunhandledrejection'); diff --git a/test/globals.js b/test/globals.js deleted file mode 100644 index bd0ed5d5a484..000000000000 --- a/test/globals.js +++ /dev/null @@ -1,2 +0,0 @@ -window.__DEV__ = true; -__DEV__ = true; diff --git a/test/integration/loader-test.js b/test/integration/loader-test.js new file mode 100644 index 000000000000..fe133b27781d --- /dev/null +++ b/test/integration/loader-test.js @@ -0,0 +1,82 @@ +/*global assert*/ +function iframeExecute(iframe, done, execute, assertCallback) { + iframe.contentWindow.done = function() { + try { + assertCallback(iframe); + done(); + } catch (e) { + done(e); + } + }; + // use setTimeout so stack trace doesn't go all the way back to mocha test runner + iframe.contentWindow.eval( + 'window.originalBuiltIns.setTimeout.call(window, ' + execute.toString() + ');' + ); +} + +function createIframe(done) { + var iframe = document.createElement('iframe'); + iframe.style.display = 'none'; + iframe.src = './base/test/integration/loader.html'; + iframe.onload = function() { + done(); + }; + document.body.appendChild(iframe); + return iframe; +} + +describe('Async SDK Loader', function() { + this.timeout(30000); + + beforeEach(function(done) { + this.iframe = createIframe(done); + }); + + afterEach(function() { + document.body.removeChild(this.iframe); + }); + + it('should capture errors before and after SDK is loaded', function(done) { + var iframe = this.iframe; + + iframeExecute( + iframe, + done, + function() { + // This is to ensure that we are indeed queuing the errors + if (typeof window.Raven !== 'undefined') { + window.loadedPreTest = true; + } else { + window.loadedPreTest = false; + } + + // Because we pre-load loader.js through karma, we don't have to worry about XHR delays etc. + setTimeout(function() { + Raven.captureException(new Error('post load exception')); + done(); + }, 1000); + }, + function() { + // Raven shouldn't be loaded synchronously, so it shouldnt be available at the beginning + assert.equal(iframe.contentWindow.loadedPreTest, false, 'A'); + // but it should be available later on + assert.equal(typeof iframe.contentWindow.Raven !== 'undefined', true, 'B'); + // and it should be configured + assert.equal(iframe.contentWindow.Raven.isSetup(), true, 'C'); + + var ravenData = iframe.contentWindow.ravenData; + + if (iframe.contentWindow.supportsOnunhandledrejection) { + assert.equal(ravenData.length, 3); + assert.equal(ravenData[0].exception.values[0].value, 'pre load exception'); + assert.equal(ravenData[1].exception.values[0].value, 'pre load rejection'); + assert.equal(ravenData[2].exception.values[0].value, 'post load exception'); + } else { + assert.equal(ravenData.length, 2); + assert.equal(ravenData[0].exception.values[0].value, 'pre load exception'); + assert.equal(ravenData[1].exception.values[0].value, 'post load exception'); + } + } + ); + }); +}); diff --git a/test/integration/loader.html b/test/integration/loader.html new file mode 100644 index 000000000000..015eef3413fb --- /dev/null +++ b/test/integration/loader.html @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + diff --git a/test/integration/test.js b/test/integration/test.js index 5552ed84b052..d06dce1a3c8e 100644 --- a/test/integration/test.js +++ b/test/integration/test.js @@ -44,7 +44,7 @@ function isEdge14() { } describe('integration', function() { - this.timeout(10000); + this.timeout(30000); beforeEach(function(done) { this.iframe = createIframe(done); From 2c64b1bba757c9ac1932b61fb91a2b9bd1182821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Og=C3=B3rek?= Date: Thu, 24 May 2018 14:47:56 +0200 Subject: [PATCH 2/5] loader: Added simple build step --- package.json | 3 ++- src/loader.js | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index deec78eda095..c6c471700337 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "test:loader-sauce": "karma start karma/karma.loader-sauce.config.js", "test:typescript": "tsc --noEmit --noImplicitAny typescript/raven-tests.ts", "test:ci": "npm run lint && grunt test:ci && npm run test:loader-sauce && npm run test:integration-sauce", - "test:size": "grunt dist && bundlesize && git checkout -- dist/" + "test:size": "grunt dist && bundlesize && git checkout -- dist/", + "loader": "cat src/loader.js | sed '/build_marker/{N;d;}' | npx google-closure-compiler-js" }, "devDependencies": { "bluebird": "^3.4.1", diff --git a/src/loader.js b/src/loader.js index ec4ca2823287..21a0952a5eef 100644 --- a/src/loader.js +++ b/src/loader.js @@ -70,9 +70,10 @@ } }); - // TODO: Figure out how to work around this for tests. We cannot just attach it without the delay - // as raven.js file is pre-loaded and XHR will behave like a synchronous call + // Markers used to remove setTimeout (used for testing) from prod build, DO NOT REMOVE + // build_marker setTimeout(function() { _currentScriptTag.parentNode.insertBefore(_newScriptTag, _currentScriptTag); + // build_marker }, 500); })(window, document, 'script', 'onerror', 'onunhandledrejection'); From c0b04e9abcb3557158b33603ec4202e247a94b6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Og=C3=B3rek?= Date: Thu, 24 May 2018 15:08:33 +0200 Subject: [PATCH 3/5] loader: Async loading documentation --- docs/install.rst | 47 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/docs/install.rst b/docs/install.rst index 21bc032813b4..0b96a398c3c5 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -8,10 +8,10 @@ So for example: .. sourcecode:: html - - - - + + + + This allows the ability for Raven's integrations to instrument themselves. If included before something like Angular, it'd be impossible to use for @@ -28,7 +28,7 @@ Our CDN distributes builds with and without :doc:`integrations + This version does not include any plugins. See `ravenjs.com `_ for more information about plugins and getting @@ -45,11 +45,11 @@ add it to ``bower.json``). .. code-block:: sh - $ bower install raven-js --save + $ bower install raven-js --save .. code-block:: html - + Also note that the file is uncompresed but is ready to pass to any decent JavaScript compressor like `UglifyJS @@ -63,7 +63,7 @@ Raven is also available as an npm package, `raven-js .. code-block:: sh - $ npm install raven-js --save + $ npm install raven-js --save .. code-block:: html @@ -96,6 +96,37 @@ To use Raven with ES2015 (ES6) imports: .config('___PUBLIC_DSN___') .install(); +Async Loading +~~~~~~~~~~~~~ + +To load Sentry JS SDK asynchronously, you need to do two things. + +Provide global ``SENTRY_SDK`` variable with SDK's URL (for example from our CDN), your DSN and SDK's configuration. +And place the snippet below as soon as possible in your HTML code. For example: + +.. code-block:: html + + + +Or you can place those two things in a separate script tags. +This will queue all errors (and promises if the environment supports ``unhandledrejection`` handler) that happened before SDK was loaded and send them once it's configured and installed. +Be aware however, that there are some trade-offs to this solution, as errors might provide less information due to them being "retriggered" instead of being caught from the original source. + +NOTE: This won't work when opening ``index.html`` or any other html file from the file system, as it doesn't support anonymous crossorigin scripts. +It has to run on the server or any local dev environment in order to correctly catch and send errors to our system. + +To read un-minified source code for this loader, see `loader.js`_ + Requirements ~~~~~~~~~~~~ From 12e60a73b7a240789edd7b3724e2b05c809624b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Og=C3=B3rek?= Date: Thu, 24 May 2018 15:10:35 +0200 Subject: [PATCH 4/5] loader: Use CDN and DSN dynamic variables --- docs/install.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/install.rst b/docs/install.rst index 0b96a398c3c5..ff143ca30e87 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -108,8 +108,8 @@ And place the snippet below as soon as possible in your HTML code. For example: -Or you can place those two things in a separate script tags. -This will queue all errors (and promises if the environment supports ``unhandledrejection`` handler) that happened before SDK was loaded and send them once it's configured and installed. +Or you can place those two things in a separate script tags. This will queue all errors (and promises if the environment supports ``unhandledrejection`` handler) that happened before SDK was loaded and send them once it's configured and installed. + Be aware however, that there are some trade-offs to this solution, as errors might provide less information due to them being "retriggered" instead of being caught from the original source. -NOTE: This won't work when opening ``index.html`` or any other html file from the file system, as it doesn't support anonymous crossorigin scripts. -It has to run on the server or any local dev environment in order to correctly catch and send errors to our system. +NOTE: This won't work when opening ``index.html`` or any other html file from the file system, as it doesn't support anonymous cross-origin scripts. +The same thing can happen for any cross-origin scripts as well. To read more about it, see `What the heck is "Script error"?`_. To read un-minified source code for this loader, see `loader.js`_ diff --git a/package.json b/package.json index c6c471700337..c3d813d1ee82 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "test:typescript": "tsc --noEmit --noImplicitAny typescript/raven-tests.ts", "test:ci": "npm run lint && grunt test:ci && npm run test:loader-sauce && npm run test:integration-sauce", "test:size": "grunt dist && bundlesize && git checkout -- dist/", - "loader": "cat src/loader.js | sed '/build_marker/{N;d;}' | npx google-closure-compiler-js" + "loader": "cat src/loader.js | sed '/build_marker/{N;d;}' | npx google-closure-compiler-js | perl -e \"print ';'; print ;\"" }, "devDependencies": { "bluebird": "^3.4.1",