diff --git a/packages/browser/package.json b/packages/browser/package.json index fecd3ff69cbf..1191413a5a7e 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -23,6 +23,7 @@ }, "devDependencies": { "@types/md5": "2.1.33", + "btoa": "^1.2.1", "chai": "^4.1.2", "chokidar": "^3.0.2", "jest": "^24.7.1", @@ -36,6 +37,7 @@ "karma-sinon": "^1.0.5", "karma-typescript": "^4.0.0", "karma-typescript-es6-transform": "^4.0.0", + "node-fetch": "^2.6.0", "npm-run-all": "^4.1.2", "prettier": "^1.17.0", "prettier-check": "^2.0.0", @@ -74,6 +76,7 @@ "test:unit:watch": "karma start test/unit/karma.conf.js --auto-watch --no-single-run", "test:integration": "test/integration/run.js", "test:integration:watch": "test/integration/run.js --watch", + "test:integration:checkbrowsers": "node scripts/checkbrowsers.js", "test:manual": "node test/manual/npm-build.js && rm test/manual/tmp.js", "size:check": "run-p size:check:es5 size:check:es6", "size:check:es5": "cat build/bundle.min.js | gzip -9 | wc -c | awk '{$1=$1/1024; print \"ES5: \",$1,\"kB\";}'", diff --git a/packages/browser/scripts/checkbrowsers.js b/packages/browser/scripts/checkbrowsers.js new file mode 100644 index 000000000000..9e6481485d79 --- /dev/null +++ b/packages/browser/scripts/checkbrowsers.js @@ -0,0 +1,132 @@ +// Script which checks all browsers in test/integration/browser.js against supported BrowserStack browsers +// Meant to be run manually, by running `yarn test:integration:checkbrowsers` from the command line + +const btoa = require('btoa'); +const fetch = require('node-fetch'); +const localConfigs = require('../test/integration/browsers.js'); + +const browserstackUsername = process.env.BROWSERSTACK_USERNAME; +const browserstackAccessKey = process.env.BROWSERSTACK_ACCESS_KEY; + +const hasCreds = () => { + return browserstackUsername !== undefined && browserstackAccessKey !== undefined; +}; + +const fetchCurrentData = (username, key) => { + const authKey = btoa(`${username}:${key}`); + + return fetch('https://api.browserstack.com/5/browsers?flat=true', { + headers: { + Authorization: `Basic ${authKey}`, + }, + }).then(response => { + if (response.status >= 200 && response.status < 300) { + return response.json(); + } else { + throw new Error(`Unable to fetch data. Status: ${response.status} ${response.statusText}`); + } + }); +}; + +const isMatchingEntry = (key, localConfig, bsConfig) => { + let localValue = localConfig[key]; + let bsValue = bsConfig[key]; + + // all values are either null, undefined, or strings, so checking truthiness should + // save us from trying to lowercase anything that can't handle it + if (localValue) { + localValue = localValue.toLowerCase(); + } + if (bsValue) { + bsValue = bsValue.toLowerCase(); + } + + if (localValue === bsValue) { + return true; + } + if (key === 'browser_version' && localValue === 'latest') { + return true; + } + + return false; +}; + +const isMatchingConfig = (localConfig, bsConfig) => { + const checkKeys = ['os', 'os_version', 'browser', 'device', 'browser_version']; + + // bail on the first non-matching entry + if (checkKeys.some(key => !isMatchingEntry(key, localConfig, bsConfig))) { + return false; + } + + // while we're here, if we've found a match on everything else, make sure + // real_mobile is up to date. Now the data *really* matches! + if (localConfig.real_mobile !== bsConfig.real_mobile) { + localConfig.real_mobile_updated = true; // flag for later + localConfig.real_mobile = bsConfig.real_mobile; + } + + return true; +}; + +const isSupported = (localConfig, supportedConfigs) => { + return supportedConfigs.some(supportedConfig => isMatchingConfig(localConfig, supportedConfig)); +}; + +const checkLocalConfigsVsBrowserStack = (localConfigs, bsConfigs) => { + const unsupportedConfigs = []; + const realMobileUpdates = []; + + // check each local config against the entire collection of BS configs + for (const configName in localConfigs) { + const localConfig = localConfigs[configName]; + + console.log(`\nChecking ${configName}`); + + if (!isSupported(localConfig, bsConfigs)) { + console.log(' UNSUPPORTED'); + unsupportedConfigs.push(configName); + } else if (localConfig.real_mobile_updated) { + console.log(' Supported (but needs real_mobile update)'); + realMobileUpdates.push(configName); + } else { + console.log(' Supported!'); + } + } + + // report on unsupported configs + if (unsupportedConfigs.length) { + console.log('\nFound unsupported browser configurations:'); + for (const configName of unsupportedConfigs) { + console.log(`\n${configName}: `, localConfigs[configName]); + } + console.log( + '\nPlease visit https://api.browserstack.com/5/browsers or https://api.browserstack.com/5/browsers?flat=true to choose new configurations.', + ); + } else { + console.log('\nAll configurations supported!\n'); + } + + // report on real_mobile updates + if (realMobileUpdates.length) { + console.log('\nFound supported browser configurations which need real_mobile updated:\n'); + for (const configName of realMobileUpdates) { + console.log(configName, 'new real_mobile value: ', localConfigs[configName].real_mobile); + } + } +}; + +const findUnsupportedConfigs = localConfigs => { + if (!hasCreds()) { + console.warn( + 'Unable to find API credentials in env. Please export them as BROWSERSTACK_USERNAME and BROWSERSTACK_ACCESS_KEY.', + ); + return; + } + + fetchCurrentData(browserstackUsername, browserstackAccessKey) + .then(data => checkLocalConfigsVsBrowserStack(localConfigs, data)) + .catch(err => console.log(err)); +}; + +findUnsupportedConfigs(localConfigs); diff --git a/packages/browser/test/integration/browsers.js b/packages/browser/test/integration/browsers.js index 710d3ba02038..86f9e2efddfc 100644 --- a/packages/browser/test/integration/browsers.js +++ b/packages/browser/test/integration/browsers.js @@ -1,51 +1,60 @@ +// To check if all of these browsers are still viable, run +// yarn test:integration:checkbrowsers + module.exports = { bs_android_4: { base: "BrowserStack", - browser: "Android", + browser: "Android Browser", device: "Google Nexus 5", os: "android", os_version: "4.4", real_mobile: true, + browser_version: null, }, bs_android_5: { base: "BrowserStack", - browser: "Android", + browser: "Android Browser", device: "Google Nexus 9", os: "android", os_version: "5.1", real_mobile: true, + browser_version: null, }, bs_android_6: { base: "BrowserStack", - browser: "Android", + browser: "Android Browser", device: "Samsung Galaxy S7", os: "android", os_version: "6.0", real_mobile: true, + browser_version: null, }, bs_android_7: { base: "BrowserStack", - browser: "Android", + browser: "Android Browser", device: "Samsung Galaxy S8", os: "android", os_version: "7.0", real_mobile: true, + browser_version: null, }, bs_android_8: { base: "BrowserStack", - browser: "Android", + browser: "Android Browser", device: "Samsung Galaxy S9", os: "android", os_version: "8.0", real_mobile: true, + browser_version: null, }, bs_android_9: { base: "BrowserStack", - browser: "Android", + browser: "Android Browser", device: "Samsung Galaxy S9 Plus", os: "android", os_version: "9.0", real_mobile: true, + browser_version: null, }, bs_ios_11: { base: "BrowserStack", @@ -54,6 +63,7 @@ module.exports = { os: "ios", os_version: "11.4", real_mobile: true, + browser_version: null, }, bs_ios_12: { base: "BrowserStack", @@ -62,6 +72,7 @@ module.exports = { os: "ios", os_version: "12.1", real_mobile: true, + browser_version: null, }, bs_ie10: { base: "BrowserStack", @@ -69,6 +80,8 @@ module.exports = { browser_version: "10.0", os: "Windows", os_version: "8", + device: null, + real_mobile: null, }, bs_ie11: { base: "BrowserStack", @@ -76,6 +89,8 @@ module.exports = { browser_version: "11.0", os: "Windows", os_version: "10", + device: null, + real_mobile: null, }, bs_safari: { base: "BrowserStack", @@ -83,6 +98,8 @@ module.exports = { browser_version: "latest", os: "OS X", os_version: "Mojave", + device: null, + real_mobile: null, }, bs_edge: { base: "BrowserStack", @@ -90,6 +107,8 @@ module.exports = { browser_version: "latest", os: "Windows", os_version: "10", + device: null, + real_mobile: null, }, bs_firefox: { base: "BrowserStack", @@ -97,6 +116,8 @@ module.exports = { browser_version: "latest", os: "Windows", os_version: "10", + device: null, + real_mobile: null, }, bs_chrome: { base: "BrowserStack", @@ -104,5 +125,7 @@ module.exports = { browser_version: "latest", os: "Windows", os_version: "10", + device: null, + real_mobile: null, }, }; diff --git a/yarn.lock b/yarn.lock index 637bfae2a355..f7e47b4a82b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2842,6 +2842,11 @@ btoa-lite@^1.0.0: resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337" integrity sha1-M3dm2hWAEhD92VbCLpxokaudAzc= +btoa@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/btoa/-/btoa-1.2.1.tgz#01a9909f8b2c93f6bf680ba26131eb30f7fa3d73" + integrity sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g== + buffer-alloc-unsafe@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" @@ -7896,6 +7901,11 @@ node-fetch@^2.2.0, node-fetch@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.3.0.tgz#1a1d940bbfb916a1d3e0219f037e89e71f8c5fa5" +node-fetch@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" + integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== + node-forge@^0.7.4: version "0.7.6" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac"