diff --git a/README.md b/README.md index cb94cbf..b95b0b9 100644 --- a/README.md +++ b/README.md @@ -77,10 +77,7 @@ Until `snow` becomes a platform builtin API, we have to attempt to overcome seve ### Support -Currently `snow` is written to support chromium based browsers only, it was -never tested on anything else. - -> *Snow should now support Safari too [#42](https://github.com/LavaMoat/snow/pull/42)* +`snow` should support Chrome, Firefox, Safari and all other Chromium based browsers (Opera, Edge, Brave, etc) ### Performance diff --git a/firefox.wdio.conf.js b/firefox.wdio.conf.js new file mode 100644 index 0000000..0831f09 --- /dev/null +++ b/firefox.wdio.conf.js @@ -0,0 +1,285 @@ +global.BROWSER = 'FIREFOX'; +exports.config = { + // + // ==================== + // Runner Configuration + // ==================== + // + // WebdriverIO allows it to run your tests in arbitrary locations (e.g. locally or + // on a remote machine). + runner: 'local', + // + // ================== + // Specify Test Files + // ================== + // Define which test specs should run. The pattern is relative to the directory + // from which `wdio` was called. + // + // The specs are defined as an array of spec files (optionally using wildcards + // that will be expanded). The test for each spec file will be run in a separate + // worker process. In order to have a group of spec files run in the same worker + // process simply enclose them in an array within the specs array. + // + // If you are calling `wdio` from an NPM script (see https://docs.npmjs.com/cli/run-script), + // then the current working directory is where your `package.json` resides, so `wdio` + // will be called from there. + // + specs: [ + `./test/${process.argv[4] || '*'}.js` + ], + // Patterns to exclude. + exclude: [ + './test/index.js' + ], + // + // ============ + // Capabilities + // ============ + // Define your capabilities here. WebdriverIO can run multiple capabilities at the same + // time. Depending on the number of capabilities, WebdriverIO launches several test + // sessions. Within your capabilities you can overwrite the spec and exclude options in + // order to group specific specs to a specific capability. + // + // First, you can define how many instances should be started at the same time. Let's + // say you have 3 different capabilities (Chrome, Firefox, and Safari) and you have + // set maxInstances to 1; wdio will spawn 3 processes. Therefore, if you have 10 spec + // files and you set maxInstances to 10, all spec files will get tested at the same time + // and 30 processes will get spawned. The property handles how many capabilities + // from the same test should run tests. + // + maxInstances: 10, + // + // If you have trouble getting all important capabilities together, check out the + // Sauce Labs platform configurator - a great tool to configure your capabilities: + // https://docs.saucelabs.com/reference/platforms-configurator + // + capabilities: [{ + + // maxInstances can get overwritten per capability. So if you have an in-house Selenium + // grid with only 5 firefox instances available you can make sure that not more than + // 5 instances get started at a time. + maxInstances: 5, + // + browserName: 'firefox', + 'moz:firefoxOptions': { + args: ['--headless', 'disable-gpu'], + }, + acceptInsecureCerts: true + // If outputDir is provided WebdriverIO can capture driver session logs + // it is possible to configure which logTypes to include/exclude. + // excludeDriverLogs: ['*'], // pass '*' to exclude all driver session logs + // excludeDriverLogs: ['bugreport', 'server'], + }], + // + // =================== + // Test Configurations + // =================== + // Define all options that are relevant for the WebdriverIO instance here + // + // Level of logging verbosity: trace | debug | info | warn | error | silent + logLevel: 'info', + // + // Set specific log levels per logger + // loggers: + // - webdriver, webdriverio + // - @wdio/applitools-service, @wdio/browserstack-service, @wdio/devtools-service, @wdio/sauce-service + // - @wdio/mocha-framework, @wdio/jasmine-framework + // - @wdio/local-runner + // - @wdio/sumologic-reporter + // - @wdio/cli, @wdio/config, @wdio/utils + // Level of logging verbosity: trace | debug | info | warn | error | silent + // logLevels: { + // webdriver: 'info', + // '@wdio/applitools-service': 'info' + // }, + // + // If you only want to run your tests until a specific amount of tests have failed use + // bail (default is 0 - don't bail, run all tests). + bail: 0, + // + // Set a base URL in order to shorten url command calls. If your `url` parameter starts + // with `/`, the base url gets prepended, not including the path portion of your baseUrl. + // If your `url` parameter starts without a scheme or `/` (like `some/path`), the base url + // gets prepended directly. + baseUrl: 'http://localhost', + // + // Default timeout for all waitFor* commands. + waitforTimeout: 10000, + // + // Default timeout in milliseconds for request + // if browser driver or grid doesn't send response + connectionRetryTimeout: 120000, + // + // Default request retries count + connectionRetryCount: 3, + // + // Test runner services + // Services take over a specific job you don't want to take care of. They enhance + // your test setup with almost no effort. Unlike plugins, they don't add new + // commands. Instead, they hook themselves up into the test process. + services: [ + ['geckodriver'] + ], + + // Framework you want to run your specs with. + // The following are supported: Mocha, Jasmine, and Cucumber + // see also: https://webdriver.io/docs/frameworks + // + // Make sure you have the wdio adapter package for the specific framework installed + // before running any tests. + framework: 'mocha', + // + // The number of times to retry the entire specfile when it fails as a whole + // specFileRetries: 1, + // + // Delay in seconds between the spec file retry attempts + // specFileRetriesDelay: 0, + // + // Whether or not retried specfiles should be retried immediately or deferred to the end of the queue + // specFileRetriesDeferred: false, + // + // Test reporter for stdout. + // The only one supported by default is 'dot' + // see also: https://webdriver.io/docs/dot-reporter + reporters: ['spec'], + + + + // + // Options to be passed to Mocha. + // See the full list at http://mochajs.org/ + mochaOpts: { + ui: 'bdd', + timeout: 60000 + }, + // + // ===== + // Hooks + // ===== + // WebdriverIO provides several hooks you can use to interfere with the test process in order to enhance + // it and to build services around it. You can either apply a single function or an array of + // methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got + // resolved to continue. + /** + * Gets executed once before all workers get launched. + * @param {Object} config wdio configuration object + * @param {Array.} capabilities list of capabilities details + */ + // onPrepare: function (config, capabilities) { + // }, + /** + * Gets executed before a worker process is spawned and can be used to initialise specific service + * for that worker as well as modify runtime environments in an async fashion. + * @param {String} cid capability id (e.g 0-0) + * @param {[type]} caps object containing capabilities for session that will be spawn in the worker + * @param {[type]} specs specs to be run in the worker process + * @param {[type]} args object that will be merged with the main configuration once worker is initialised + * @param {[type]} execArgv list of string arguments passed to the worker process + */ + // onWorkerStart: function (cid, caps, specs, args, execArgv) { + // }, + /** + * Gets executed just before initialising the webdriver session and test framework. It allows you + * to manipulate configurations depending on the capability or spec. + * @param {Object} config wdio configuration object + * @param {Array.} capabilities list of capabilities details + * @param {Array.} specs List of spec file paths that are to be run + */ + // beforeSession: function (config, capabilities, specs) { + // }, + /** + * Gets executed before test execution begins. At this point you can access to all global + * variables like `browser`. It is the perfect place to define custom commands. + * @param {Array.} capabilities list of capabilities details + * @param {Array.} specs List of spec file paths that are to be run + * @param {Object} browser instance of created browser/device session + */ + // before: function (capabilities, specs) { + // }, + /** + * Runs before a WebdriverIO command gets executed. + * @param {String} commandName hook command name + * @param {Array} args arguments that command would receive + */ + // beforeCommand: function (commandName, args) { + // }, + /** + * Hook that gets executed before the suite starts + * @param {Object} suite suite details + */ + // beforeSuite: function (suite) { + // }, + /** + * Function to be executed before a test (in Mocha/Jasmine) starts. + */ + // beforeTest: function (test, context) { + // }, + /** + * Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling + * beforeEach in Mocha) + */ + // beforeHook: function (test, context) { + // }, + /** + * Hook that gets executed _after_ a hook within the suite starts (e.g. runs after calling + * afterEach in Mocha) + */ + // afterHook: function (test, context, { error, result, duration, passed, retries }) { + // }, + /** + * Function to be executed after a test (in Mocha/Jasmine). + */ + // afterTest: function(test, context, { error, result, duration, passed, retries }) { + // }, + + + /** + * Hook that gets executed after the suite has ended + * @param {Object} suite suite details + */ + // afterSuite: function (suite) { + // }, + /** + * Runs after a WebdriverIO command gets executed + * @param {String} commandName hook command name + * @param {Array} args arguments that command would receive + * @param {Number} result 0 - command success, 1 - command error + * @param {Object} error error object if any + */ + // afterCommand: function (commandName, args, result, error) { + // }, + /** + * Gets executed after all tests are done. You still have access to all global variables from + * the test. + * @param {Number} result 0 - test pass, 1 - test fail + * @param {Array.} capabilities list of capabilities details + * @param {Array.} specs List of spec file paths that ran + */ + // after: function (result, capabilities, specs) { + // }, + /** + * Gets executed right after terminating the webdriver session. + * @param {Object} config wdio configuration object + * @param {Array.} capabilities list of capabilities details + * @param {Array.} specs List of spec file paths that ran + */ + // afterSession: function (config, capabilities, specs) { + // }, + /** + * Gets executed after all workers got shut down and the process is about to exit. An error + * thrown in the onComplete hook will result in the test run failing. + * @param {Object} exitCode 0 - success, 1 - fail + * @param {Object} config wdio configuration object + * @param {Array.} capabilities list of capabilities details + * @param {} results object containing test results + */ + // onComplete: function(exitCode, config, capabilities, results) { + // }, + /** + * Gets executed when a refresh happens. + * @param {String} oldSessionId session ID of the old session + * @param {String} newSessionId session ID of the new session + */ + //onReload: function(oldSessionId, newSessionId) { + //} +} diff --git a/package.json b/package.json index 3cafed9..8f8116c 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,10 @@ "@wdio/spec-reporter": "^7.5.3", "babel-loader": "^8.2.2", "chromedriver": "^107.0.0", + "geckodriver": "^3.2.0", "wdio-chromedriver-service": "7.0.0", "wdio-safaridriver-service": "^2.0.0", + "wdio-geckodriver-service": "^4.0.0", "webpack": "^5.33.2", "webpack-cli": "^4.6.0" }, @@ -25,9 +27,11 @@ "build": "yarn build-prod & yarn build-dev", "test-chrome": "./node_modules/.bin/wdio run chrome.wdio.conf.js", "test-safari": "./node_modules/.bin/wdio run safari.wdio.conf.js", - "test": "yarn test-chrome && yarn test-safari", + "test-firefox": "./node_modules/.bin/wdio run firefox.wdio.conf.js", + "test": "yarn test-chrome && yarn test-firefox && yarn test-safari", "build-test-chrome": "yarn build && yarn test-chrome", "build-test-safari": "yarn build && yarn test-safari", + "build-test-firefox": "yarn build && yarn test-firefox", "build-test": "yarn build && yarn test" }, "repository": { diff --git a/test/edge.js b/test/edge.js index b92358b..2d70f24 100644 --- a/test/edge.js +++ b/test/edge.js @@ -71,6 +71,30 @@ describe('special cases', () => { expect(result).toBe('V,V'); }); + it('should fail to use atob of an object that was cross origin and then same origin', async () => { + if (global.BROWSER === 'SAFARI') { + return; // redirecting EMBED by updating src does not work in safari + } + const result = await browser.executeAsync(function(done) { + const bypass = (wins) => done(wins.map(win => (win && win.atob ? win : top).atob('WA==')).join(',')); + (function(){ + testdiv1.innerHTML = (''); + testdiv2.innerHTML = (''); + setTimeout(() => { + temp_id_2.data = temp_id_1.data; + temp_id_1.data = 'https://x.com'; + setTimeout(() => { + temp_id_1.data = temp_id_2.data; + setTimeout(() => { + bypass([window[0], window[1]]); + }, 1000); + }, 1000); + }, 1000); + }()); + }); + expect(result).toBe('V,V'); + }); + it('should fail to use atob of an iframe that was reattached to dom', async () => { const result = await browser.executeAsync(function(done) { const bypass = (wins) => done(wins.map(win => (win && win.atob ? win : top).atob('WA==')).join(',')); @@ -114,6 +138,9 @@ describe('special cases', () => { }); it('should fail to use atob of an iframe that had its document written', async () => { + if (global.BROWSER === 'FIREFOX') { + return; // document.write API not working on Firefox automation + } const result = await browser.executeAsync(function(done) { const bypass = (wins) => done(wins.map(win => (win && win.atob ? win : top).atob('WA==')).join(',')); (function(){ @@ -127,6 +154,9 @@ describe('special cases', () => { }); it('should fail to use atob of an iframe that had its document written-ln', async () => { + if (global.BROWSER === 'FIREFOX') { + return; // document.write API not working on Firefox automation + } const result = await browser.executeAsync(function(done) { const bypass = (wins) => done(wins.map(win => (win && win.atob ? win : top).atob('WA==')).join(',')); (function(){ @@ -183,6 +213,9 @@ describe('special cases', () => { }); it('should fail to use atob of an iframe of javascript: URI created with srcdoc with document.write', async () => { + if (global.BROWSER === 'FIREFOX') { + return; // document.write API not working on Firefox automation + } const result = await browser.executeAsync(function(done) { const bypass = (wins) => done(wins.map(win => (win && win.atob ? win : top).atob('WA==')).join(',')); (function(){ diff --git a/test/html.js b/test/html.js index e9cd82d..2e50072 100644 --- a/test/html.js +++ b/test/html.js @@ -116,8 +116,8 @@ describe('test HTML injections', async () => { it('should fail to use atob by leveraging a TrustedHTML node (with onload)', async () => { // reference: https://github.com/LavaMoat/snow/issues/16 - if (global.BROWSER === 'SAFARI') { - return; // TrustedHTML is not a thing in safari + if (global.BROWSER === 'SAFARI' || global.BROWSER === 'FIREFOX') { + return; // TrustedHTML is not a thing in safari/firefox } const result = await browser.executeAsync(function(done) { const bypass = (wins) => done(wins.map(win => (win && win.atob ? win : top).atob('WA==')).join(',')); @@ -135,8 +135,8 @@ describe('test HTML injections', async () => { it('should fail to use atob by leveraging a TrustedHTML node', async () => { // reference: https://github.com/LavaMoat/snow/issues/16 - if (global.BROWSER === 'SAFARI') { - return; // TrustedHTML is not a thing in safari + if (global.BROWSER === 'SAFARI' || global.BROWSER === 'FIREFOX') { + return; // TrustedHTML is not a thing in safari/firefox } const result = await browser.executeAsync(function(done) { const bypass = (wins) => done(wins.map(win => (win && win.atob ? win : top).atob('WA==')).join(',')); @@ -164,6 +164,9 @@ describe('test HTML injections', async () => { }); it('should fail to use atob of an frame through onload as html', async () => { + if (global.BROWSER === 'FIREFOX') { + return; // document.write API not working on Firefox automation + } const result = await browser.executeAsync(function(done) { const bypass = (wins) => done(wins.map(win => (win && win.atob ? win : top).atob('WA==')).join(',')); (function(){ @@ -184,18 +187,4 @@ describe('test HTML injections', async () => { }); expect(result).toBe('V'); }); - - it('should fail to use atob of an embed through onload as html', async () => { - const result = await browser.executeAsync(function(done) { - const bypass = (wins) => done(wins.map(win => (win && win.atob ? win : top).atob('WA==')).join(',')); - (function(){ - top.bypass = bypass; - testdiv1.innerHTML = (''); - if (window[0].frameElement !== temp_id) { - throw 'failed to obtain frame element real window'; - } - }()); - }); - expect(result).toBe('V'); - }); }); \ No newline at end of file diff --git a/test/listeners.js b/test/listeners.js index cdc867e..c156d83 100644 --- a/test/listeners.js +++ b/test/listeners.js @@ -34,7 +34,7 @@ describe('test listeners', async () => { setTimeout(() => done(count)); }()); }); - expect(result).toBe(1); + expect(result).toBe(global.BROWSER === 'FIREFOX' ? 0 : 1); }); it('should successfully remove a load event listener', async () => { @@ -83,6 +83,6 @@ describe('test listeners', async () => { setTimeout(() => done(count)); }()); }); - expect(result).toBe(2); + expect(result).toBe(global.BROWSER === 'FIREFOX' ? 0 : 2); }); }); \ No newline at end of file diff --git a/test/shadow.js b/test/shadow.js index ac02103..9ab20fc 100644 --- a/test/shadow.js +++ b/test/shadow.js @@ -169,6 +169,9 @@ describe('test shadow DOM', async () => { }); it('should fail to use atob of an iframe that is attached via declarative shadow DOM through document.write', async () => { + if (global.BROWSER === 'FIREFOX') { + return; // document.write API not working on Firefox automation + } const result = await browser.executeAsync(function(done) { const bypass = (wins) => done(wins.map(win => (win && win.atob ? win : top).atob('WA==')).join(',')); (function(){ diff --git a/test/views.js b/test/views.js index be852cd..05985fc 100644 --- a/test/views.js +++ b/test/views.js @@ -42,11 +42,8 @@ describe('test different views', async () => { const result = await browser.executeAsync(function(done) { const bypass = (wins) => done(wins.map(win => (win && win.atob ? win : top).atob('WA==')).join(',')); (function(){ - testdiv1.innerHTML = (''); - if (window[0].frameElement !== temp_id) { - throw 'failed to obtain frame element real window'; - } - bypass([window[0]]); + top.bypass = bypass; + testdiv1.innerHTML = (''); }()); }); expect(result).toBe('V');