From f33c193600cac7e8478bf8076bf060bf7d59621f Mon Sep 17 00:00:00 2001 From: Chris Breiding Date: Tue, 24 Sep 2019 14:35:24 -0400 Subject: [PATCH] Electron v5.0.10 (#4720) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix specs * use debugger protocol for cookie handling in electron * use latest gulp * use rimraf instead of gulp-clean * use electron 3.1.8 and node 10.2.1 * use gulp 4 in packages/static * fix sendCommandAsync, log Schema.getDomains on CDP connect * autofill e2e test name [skip ci] * electron@5.0.7, see what new failures exist * --no-sandbox for launching Electron * update cookies logic for electron * node 12 * update snapshot for new node * update error message for new node * stub sendCommandAsync * only connect to socket if path has been replaced, fixes #4776 * update node-sass to support node 12 * skip wacky socket tests for now * snapshot * fix run_plugins_spec snapshot, don't include stack trace * use --no-sandbox on linux to run as root * allow sendCommandAsync to resolve * use euid for root check * log domains even if undefined * don't worry about ending 1xx responses immediately anymore * use --max-http-header-size, change max size from 8kb to 1mb, fix #76 * do not send 502 on failed websocket, just send back ECONNRESET * update websocket spec port to not collide with other test * update outdated expect * Revert "only connect to socket if path has been replaced, fixes #4776" This reverts commit f179eda5cac23437e72bea0d7a6f0b3733e9d485. * update gulp in root * update https-proxy unit tests * update network spec to properly close server * update reporter spec * update https-proxy-agent to fix node 10.10.0 change discussion: https://github.com/nodejs/node/issues/24474\#issuecomment-511963799 * only pass --max-http-header-size on node >=12 * use own server-destroy implementation that supports secureConnect events * oops * update socket_spec * electron 6.0.0 * console.table introduced in node 10 * change browserify entry to init.js * handle edge case when no response body * console.table added in node 10 * do not exit app when all BrowserWindows are closed * update e2e snapshots * value may not be null * update plugins spec * correct cookie expiry, use browser.getversion for CDP version check * fix snapshotting for require stacks * reorder cookies in spec * warn when depreated electron callback apis are used * only report 1 plugin error per process * cleanup * node 12.4.0, cypress/browsers:node12.4.0-chrome76 docker image * update shell.openExternal to promisified * update dialog.showOpenDialog to promisified * update webContents.session.setProxy to promisified * updating native dependencies since we don't need ancient node ABI support anymore * WIP: switch cookies to simpler, jar-less approach * WIP: switch cookies to simpler, jar-less approach * making tests pass * improve cookie filtering logic * Remove unneeded Promise.try * filter what makes it to the extension * properly re-set superdomain cookies on cross-origin cy.visit * allow comma-separated list of e2e tests * sort cookies in order of expiration date, ascending * updating tests, cleanup * update tests * version electron as a devDependency, electron@6.0.1 * cleanup, remove old automation * cleanup, remove old automation * bump chokidar to fix win10 + node12 issue was seeing this on windows: https://github.com/nuxt/nuxt.js/issues/6035 fixed with version bump * enable now-supported quit role, re-enable old tests * don't need that arg there * remove last deprecated callback electron invocations * Delete cypress.json * responding to PR feedback * cleanup * invoke * use 'quit' role * Use new appMenu role for Cypress menu on mac * electron@6.0.2 * electron@6.0.3 * remove domain: cookie.domain and see what happens * remove setErrorHandler * Revert "remove domain: cookie.domain and see what happens" This reverts commit 49e916896d8ed2bb0d1c6cd59c03726d70874332. * add unit tests for cookies * ci * fix project-content css * electron@6.0.4 * fix specs_list test * electron@6.0.7 * some cleanup * electron@6.0.9 * Update 8_reporters_spec.coffee.js * electron@5.0.10 - Chromium 73, Node 12 * cli: fix the STDIN pipe on Windows (#5045) * cli: pipe stdin * uggh, here is the actual change * update cli unit tests * add unit test * more permissive check for json to include application/vnd.api+j… (#5166) * more permissive check for json to include * add json test for content-type application/vnd.api+json * cruder solution passes e2e tests locally, so let's go with that * Remove 'charset' from content-type before checking if JSON * fix eslint for fixture specs (#5176) * update eslint to lint files within 'fixtures' in support files - ignore some edge cases like jquery, jsx and obvious js files we wrote with broken code * Fixes from eslint to 'fixtures' files * Catch env variable with reserved name CYPRESS_ENV 1621 (#1626) * server: check CYPRESS_ENV variable when merging configs * catch invalid CYPRESS_ENV value in CLI, close #1621 * linting * sanitize platform in test snapshot * linting * update error message text * add missing comma * fix finally merge in JS code * pass CLI linter * fix log reference, should be debug * use correct sinon reference * update message, show first part in red * update error message text * Addresses #2953 (#5174) * Addresses #2953 * Added proper test for new error message * Didn't realize it ran this test as well, whoops * Implementing changes as suggested by @jennifer-shehane * Fixing tests and error output. Moved the checks to the start of the get command to ensure we always catch improper options * Removing issue test since the querying spec covers it * Using coffescript isArray check * depromisify things that were promisified b/t electron 5 <=> 6 Revert "update shell.openExternal to promisified" This reverts commit 8b6460d015eb85d10a38d4e234fff99b7da91e01. Revert "update dialog.showOpenDialog to promisified" This reverts commit 5f178b075bcb46daab8564736e0a318c2d21a431. Revert "update webContents.session.setProxy to promisified" This reverts commit 727df3a4e5469effcab9796acd07c45b18262c33. * node12.4.0-chrome76 => node12.0.0-chrome75 * fix tests for electron downgrade * node12.0.0-chrome75 => node12.0.0-chrome73 Co-authored-by: Zach Bloomquist Co-authored-by: Brian Mann --- .gitignore | 4 +- .node-version | 2 +- .vscode/terminals.json | 2 +- appveyor.yml | 2 +- circle.yml | 2 +- package.json | 3 +- .../cypress/integration/login_spec.js | 9 - packages/desktop-gui/src/auth/auth-api.js | 1 - packages/desktop-gui/src/lib/ipc.js | 2 - packages/desktop-gui/src/project/project.scss | 1 + packages/desktop-gui/src/specs/specs.scss | 1 + packages/driver/src/cy/stability.coffee | 10 +- packages/electron/lib/electron.coffee | 13 +- packages/electron/lib/install.coffee | 4 +- packages/electron/package.json | 4 +- packages/example/gulpfile.js | 41 ++- packages/extension/app/background.coffee | 8 +- packages/extension/gulpfile.js | 102 +++--- packages/extension/package.json | 2 +- .../test/integration/proxy_spec.coffee | 2 +- packages/network/test/unit/agent_spec.ts | 2 +- packages/reporter/test/helper.js | 2 +- ...caught_uncaught_hook_errors_spec.coffee.js | 1 - .../1_commands_outside_of_test_spec.coffee.js | 172 ++++++++++ .../__snapshots__/2_config_spec.coffee.js | 1 - .../__snapshots__/2_cookies_spec.coffee.js | 73 +++- .../2_form_submissions_spec.coffee.js | 1 - .../__snapshots__/3_issue_173_spec.coffee.js | 1 - .../__snapshots__/3_plugins_spec.coffee.js | 1 - .../__snapshots__/4_promises_spec.coffee.js | 1 - .../__snapshots__/4_request_spec.coffee.js | 221 +++++++----- .../4_return_value_spec.coffee.js | 1 - .../5_spec_isolation_spec.coffee.js | 40 +-- .../__snapshots__/5_stdout_spec.coffee.js | 2 - .../__snapshots__/5_subdomain_spec.coffee.js | 74 +++- .../5_task_not_registered_spec.coffee.js | 1 - .../__snapshots__/6_task_spec.coffee.js | 7 +- .../__snapshots__/6_visit_spec.coffee.js | 20 -- .../__snapshots__/8_reporters_spec.coffee.js | 11 +- .../__snapshots__/run_plugins_spec.coffee.js | 53 --- packages/server/index.js | 9 +- packages/server/lib/automation/cookies.coffee | 69 ++-- packages/server/lib/browsers/electron.coffee | 152 +++++++-- packages/server/lib/controllers/proxy.coffee | 9 +- packages/server/lib/gui/auth.js | 4 +- packages/server/lib/gui/events.coffee | 7 - packages/server/lib/gui/menu.js | 43 +-- packages/server/lib/gui/windows.coffee | 59 ---- packages/server/lib/plugins/index.coffee | 1 + packages/server/lib/request.coffee | 315 +++++------------- packages/server/lib/server.coffee | 48 +-- .../server/lib/util/chrome_policy_check.js | 2 +- packages/server/lib/util/cors.js | 114 ++++--- packages/server/lib/util/electron_app.js | 9 + packages/server/package.json | 11 +- .../1_commands_outside_of_test_spec.coffee | 12 - .../server/test/e2e/2_cookies_spec.coffee | 17 +- .../server/test/e2e/4_request_spec.coffee | 32 +- .../server/test/e2e/5_subdomain_spec.coffee | 25 +- .../test/integration/cypress_spec.coffee | 10 +- .../integration/http_requests_spec.coffee | 2 +- .../test/integration/server_spec.coffee | 5 +- .../test/integration/websockets_spec.coffee | 12 +- .../performance/proxy_performance_spec.js | 11 +- packages/server/test/scripts/e2e.js | 4 +- packages/server/test/scripts/run.js | 12 + .../cypress/integration/subdomain_spec.coffee | 2 +- .../server/test/support/helpers/e2e.coffee | 1 + .../test/unit/browsers/electron_spec.coffee | 112 ++++++- .../server/test/unit/gui/events_spec.coffee | 24 -- packages/server/test/unit/gui/menu_spec.js | 48 +-- .../server/test/unit/gui/windows_spec.coffee | 172 ---------- .../plugins/child/run_plugins_spec.coffee | 6 +- packages/server/test/unit/request_spec.coffee | 36 +- packages/server/test/unit/socket_spec.coffee | 2 +- packages/static/gulpfile.js | 6 +- packages/web-config/package.json | 2 +- scripts/binary/bump.coffee | 10 +- scripts/run-docker-local.sh | 2 +- 79 files changed, 1151 insertions(+), 1166 deletions(-) diff --git a/.gitignore b/.gitignore index 80e31ba98136..b2239970cfb2 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ Cached Theme Material Design.pak packages/https-proxy/ca/ # from desktop-gui +packages/desktop-gui/cypress/videos packages/desktop-gui/src/jsconfig.json # from driver @@ -32,9 +33,6 @@ packages/example/app packages/example/build packages/example/cypress -# from driver -packages/driver/test/cypress/videos - # from server packages/server/.cy packages/server/.projects diff --git a/.node-version b/.node-version index 22333f1ec56f..f8c17e780909 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -8.9.3 +12.4.0 diff --git a/.vscode/terminals.json b/.vscode/terminals.json index c566965a74aa..6f5b1d776bf0 100644 --- a/.vscode/terminals.json +++ b/.vscode/terminals.json @@ -29,7 +29,7 @@ "onlySingle": true, "execute": false, "cwd": "[cwd]/packages/server", - "command": "npm run test-e2e -- --spec name" + "command": "npm run test-e2e -- --spec [fileBasename]" }, { "name": "packages/runner watch", diff --git a/appveyor.yml b/appveyor.yml index a0ab300d40e2..22874c15d49f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,7 +7,7 @@ branches: # https://www.appveyor.com/docs/lang/nodejs-iojs/ environment: # use matching version of Node.js - nodejs_version: "8.9.3" + nodejs_version: "12.4.0" # encode secure variables which will NOT be used # in pull requests # https://www.appveyor.com/docs/build-configuration/#secure-variables diff --git a/circle.yml b/circle.yml index d1824b323ac9..9b70244b6f31 100644 --- a/circle.yml +++ b/circle.yml @@ -23,7 +23,7 @@ executors: # the Docker image with Cypress dependencies and Chrome browser cy-doc: docker: - - image: cypress/browsers:node8.9.3-npm6.10.1-chrome75 + - image: cypress/browsers:node12.0.0-chrome73 environment: PLATFORM: linux diff --git a/package.json b/package.json index 48c1e7cc4b94..0172618d2394 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,6 @@ "cloudflare-cli": "3.2.2", "coffeelint": "1.16.2", "common-tags": "1.8.0", - "console.table": "0.10.0", "debug": "4.1.1", "decaffeinate": "6.0.1", "del": "3.0.0", @@ -161,7 +160,7 @@ "vinyl-paths": "2.1.0" }, "engines": { - "node": ">=8.9.3" + "node": "12.4.0" }, "productName": "Cypress", "license": "MIT", diff --git a/packages/desktop-gui/cypress/integration/login_spec.js b/packages/desktop-gui/cypress/integration/login_spec.js index 484f5d77a523..91ec33e4dc4c 100644 --- a/packages/desktop-gui/cypress/integration/login_spec.js +++ b/packages/desktop-gui/cypress/integration/login_spec.js @@ -18,7 +18,6 @@ describe('Login', function () { cy.stub(this.ipc, 'openProject').resolves(this.config) cy.stub(this.ipc, 'getSpecs').yields(null, this.specs) cy.stub(this.ipc, 'externalOpen') - cy.stub(this.ipc, 'clearGithubCookies') cy.stub(this.ipc, 'logOut').resolves() cy.stub(this.ipc, 'onAuthMessage').callsFake((function (_this) { @@ -124,14 +123,6 @@ describe('Login', function () { cy.get('.nav').contains('Log In') }) - it('calls clear:github:cookies', function () { - cy.get('nav a').contains('Jane').click() - - cy.contains('Log Out').click().then(function () { - expect(this.ipc.clearGithubCookies).to.be.called - }) - }) - it('calls log:out', function () { cy.get('nav a').contains('Jane').click() diff --git a/packages/desktop-gui/src/auth/auth-api.js b/packages/desktop-gui/src/auth/auth-api.js index e8010b84be39..0da2a4fba17a 100644 --- a/packages/desktop-gui/src/auth/auth-api.js +++ b/packages/desktop-gui/src/auth/auth-api.js @@ -39,7 +39,6 @@ class AuthApi { logOut () { authStore.setUser(null) - ipc.clearGithubCookies() ipc.logOut() .catch((err) => { err.name = 'An unexpected error occurred while logging out' diff --git a/packages/desktop-gui/src/lib/ipc.js b/packages/desktop-gui/src/lib/ipc.js index 874d819aea7c..5eb2512449c4 100644 --- a/packages/desktop-gui/src/lib/ipc.js +++ b/packages/desktop-gui/src/lib/ipc.js @@ -11,7 +11,6 @@ const ipc = { handleUnauthed () { authStore.setUser(null) - ipc.clearGithubCookies() ipc.logOut() }, } @@ -32,7 +31,6 @@ const register = (eventName, isPromiseApi = true) => { register('add:project') register('begin:auth') register('on:auth:message', false) -register('clear:github:cookies') register('close:browser') register('close:project') register('external:open') diff --git a/packages/desktop-gui/src/project/project.scss b/packages/desktop-gui/src/project/project.scss index f906087d3981..a2f149c903bd 100644 --- a/packages/desktop-gui/src/project/project.scss +++ b/packages/desktop-gui/src/project/project.scss @@ -4,4 +4,5 @@ flex-grow: 2; margin-bottom: 0; width: 100%; + min-height: 0; } diff --git a/packages/desktop-gui/src/specs/specs.scss b/packages/desktop-gui/src/specs/specs.scss index decbe8b902cb..499b4b76b4f1 100644 --- a/packages/desktop-gui/src/specs/specs.scss +++ b/packages/desktop-gui/src/specs/specs.scss @@ -5,6 +5,7 @@ $max-nesting-level: 14; display: flex; flex-direction: column; width: 100%; + min-height: 0; .empty-well code { display: block; diff --git a/packages/driver/src/cy/stability.coffee b/packages/driver/src/cy/stability.coffee index 29c451c689cb..3f36d3b0c020 100644 --- a/packages/driver/src/cy/stability.coffee +++ b/packages/driver/src/cy/stability.coffee @@ -5,20 +5,20 @@ tryFn = (fn) -> Promise.try(fn) create = (Cypress, state) -> - isStable = (bool = true, event) -> - return if state("isStable") is bool + isStable = (stable = true, event) -> + return if state("isStable") is stable ## if we are going back to stable and we have ## a whenStable callback - if bool and whenStable = state("whenStable") + if stable and whenStable = state("whenStable") ## invoke it whenStable() - state("isStable", bool) + state("isStable", stable) ## we notify the outside world because this is what the runner uses to ## show the 'loading spinner' during an app page loading transition event - Cypress.action("cy:stability:changed", bool, event) + Cypress.action("cy:stability:changed", stable, event) whenStable = (fn) -> ## if we are not stable diff --git a/packages/electron/lib/electron.coffee b/packages/electron/lib/electron.coffee index c8476a29ec19..f1631e9353f5 100644 --- a/packages/electron/lib/electron.coffee +++ b/packages/electron/lib/electron.coffee @@ -1,5 +1,6 @@ fs = require("fs-extra") cp = require("child_process") +os = require("os") path = require("path") debug = require("debug")("cypress:electron") Promise = require("bluebird") @@ -59,6 +60,10 @@ module.exports = { .then -> execPath = paths.getPathToExec() + ## if running as root, no-sandbox must be passed or Chrome will not start + if os.platform() == "linux" && process.geteuid() == 0 + argv.unshift("--no-sandbox") + ## we have an active debugger session if inspector.url() dp = process.debugPort + 1 @@ -70,6 +75,10 @@ module.exports = { if opts.inspectBrk argv.unshift("--inspect-brk=5566") + ## max HTTP header size 8kb -> 1mb + ## https://github.com/cypress-io/cypress/issues/76 + argv.unshift("--max-http-header-size=#{1024*1024}") + debug("spawning %s with args", execPath, argv) if debug.enabled @@ -77,8 +86,8 @@ module.exports = { argv.push("--enable-logging") cp.spawn(execPath, argv, {stdio: "inherit"}) - .on "close", (code) -> - debug("electron closing with code", code) + .on "close", (code, errCode) -> + debug("electron closing %o", { code, errCode }) if code debug("original command was") diff --git a/packages/electron/lib/install.coffee b/packages/electron/lib/install.coffee index 6e9644cc92d5..f2120e1fc15d 100644 --- a/packages/electron/lib/install.coffee +++ b/packages/electron/lib/install.coffee @@ -10,8 +10,8 @@ log = require("debug")("cypress:electron") fs = Promise.promisifyAll(fs) ## ensure we have an electronVersion set in package.json -if not electronVersion = pkg.electronVersion - throw new Error("Missing 'electronVersion' in ./package.json") +if not electronVersion = pkg.devDependencies.electron + throw new Error("Missing 'electron' devDependency in ./package.json") module.exports = { checkCurrentVersion: -> diff --git a/packages/electron/package.json b/packages/electron/package.json index 0bcb754a2cd5..a4a1b21bbac0 100644 --- a/packages/electron/package.json +++ b/packages/electron/package.json @@ -26,6 +26,7 @@ "minimist": "1.2.0" }, "devDependencies": { + "electron": "5.0.10", "mocha": "3.5.3" }, "files": [ @@ -34,6 +35,5 @@ ], "bin": { "cypress-electron": "./bin/cypress-electron" - }, - "electronVersion": "2.0.18" + } } diff --git a/packages/example/gulpfile.js b/packages/example/gulpfile.js index 8943c01c94d2..d0db94ca50c7 100644 --- a/packages/example/gulpfile.js +++ b/packages/example/gulpfile.js @@ -1,10 +1,10 @@ -let gulp = require('gulp') -let ghPages = require('gulp-gh-pages-will') -let clean = require('gulp-clean') -let RevAll = require('gulp-rev-all') +const gulp = require('gulp') +const ghPages = require('gulp-gh-pages-will') +const gulpClean = require('gulp-clean') +const RevAll = require('gulp-rev-all') -gulp.task('assets', function () { - let revAllOpts = { +const assets = () => { + const revAllOpts = { dontGlobal: ['.ico', 'fira.css', 'javascript-logo.png'], dontRenameFile: ['.ico', '.html', /fonts/], dontSearchFile: ['.js'], @@ -14,28 +14,25 @@ gulp.task('assets', function () { return gulp.src('./app/**/*') .pipe(RevAll.revision(revAllOpts)) .pipe(gulp.dest('build')) -}) +} -gulp.task('cname', function () { - return gulp.src('CNAME') +const cname = () => { + return gulp.src('CNAME', { allowEmpty: true }) .pipe(gulp.dest('build')) -}) +} -gulp.task('gitignore', function () { - return gulp.src('.gitignore', { allowEmpty: true }) - .pipe(gulp.dest('build')) -}) - -gulp.task('clean', function () { +const clean = () => { return gulp.src('./build', { allowEmpty: true }) - .pipe(clean()) -}) + .pipe(gulpClean()) +} -gulp.task('push-gh-pages', function () { +const pushGhPages = () => { return gulp.src('build/**/*') .pipe(ghPages()) -}) +} + +const build = gulp.series(clean, gulp.parallel(assets, cname)) -gulp.task('build', gulp.series('clean', gulp.parallel('assets', 'cname', 'gitignore'))) +exports.build = build -gulp.task('deploy', gulp.series('build', 'push-gh-pages')) +exports.deploy = gulp.series(build, pushGhPages) diff --git a/packages/extension/app/background.coffee b/packages/extension/app/background.coffee index 095a917af0f0..6fab3c48b1ca 100644 --- a/packages/extension/app/background.coffee +++ b/packages/extension/app/background.coffee @@ -4,6 +4,10 @@ once = require("lodash/once") Promise = require("bluebird") client = require("./client") +COOKIE_PROPS = ['url', 'name', 'domain', 'path', 'secure', 'storeId'] +GET_ALL_PROPS = COOKIE_PROPS.concat(['session']) +SET_PROPS = COOKIE_PROPS.concat(['value', 'httpOnly', 'expirationDate']) + httpRe = /^http/ firstOrNull = (cookies) -> @@ -61,7 +65,7 @@ connect = (host, path) -> ws.emit("automation:client:connected") return ws - + automation = { connect @@ -85,6 +89,7 @@ automation = { .map(clear) getAll: (filter = {}) -> + filter = pick(filter, GET_ALL_PROPS) get = -> new Promise (resolve) -> chrome.cookies.getAll(filter, resolve) @@ -105,6 +110,7 @@ automation = { new Promise (resolve, reject) => ## only get the url if its not already set props.url ?= @getUrl(props) + props = pick(props, SET_PROPS) chrome.cookies.set props, (details) -> switch when details diff --git a/packages/extension/gulpfile.js b/packages/extension/gulpfile.js index 888b965e69a4..9d2b9f7631ea 100644 --- a/packages/extension/gulpfile.js +++ b/packages/extension/gulpfile.js @@ -1,41 +1,36 @@ -/* eslint-disable - @cypress/dev/no-return-before, - no-unused-vars, -*/ const fs = require('fs-extra') const pkg = require('./package.json') const gulp = require('gulp') -const clean = require('gulp-clean') -const rename = require('gulp-rename') +const rimraf = require('rimraf') const source = require('vinyl-source-stream') const coffeeify = require('coffeeify') const browserify = require('browserify') -const icons = require('@cypress/icons') +const cypressIcons = require('@cypress/icons') -gulp.task('clean', () => { - return gulp.src('dist', { allowEmpty: true }) - .pipe(clean()) -}) +const copySocketClient = () => { + return gulp.src(require('../socket').getPathToClientSource()) + .pipe(gulp.dest('dist')) +} + +const clean = (done) => { + rimraf('dist', done) +} -gulp.task('manifest', (done) => { - return gulp.src('app/manifest.json') +const manifest = (done) => { + gulp.src('app/manifest.json') .pipe(gulp.dest('dist')) .on('end', () => { - return fs.readJson('dist/manifest.json', (err, json) => { + return fs.readJson('dist/manifest.json', function (err, json) { json.version = pkg.version return fs.writeJson('dist/manifest.json', json, { spaces: 2 }, done) }) }) -}) -gulp.task('backup', () => { - return gulp.src('dist/background.js') - .pipe(rename('background_src.js')) - .pipe(gulp.dest('dist')) -}) + return null +} -gulp.task('background', () => { +const background = () => { return browserify({ entries: 'app/init.js', transform: coffeeify, @@ -43,46 +38,57 @@ gulp.task('background', () => { .bundle() .pipe(source('background.js')) .pipe(gulp.dest('dist')) -}) +} -gulp.task('html', () => { +const html = () => { return gulp.src('app/**/*.html') .pipe(gulp.dest('dist')) -}) +} -gulp.task('css', () => { +const css = () => { return gulp.src('app/**/*.css') .pipe(gulp.dest('dist')) -}) +} -gulp.task('icons', () => { +const icons = () => { return gulp.src([ - icons.getPathToIcon('icon_16x16.png'), - icons.getPathToIcon('icon_19x19.png'), - icons.getPathToIcon('icon_38x38.png'), - icons.getPathToIcon('icon_48x48.png'), - icons.getPathToIcon('icon_128x128.png'), + cypressIcons.getPathToIcon('icon_16x16.png'), + cypressIcons.getPathToIcon('icon_19x19.png'), + cypressIcons.getPathToIcon('icon_38x38.png'), + cypressIcons.getPathToIcon('icon_48x48.png'), + cypressIcons.getPathToIcon('icon_128x128.png'), ]) .pipe(gulp.dest('dist/icons')) -}) +} -gulp.task('logos', () => { +const logos = () => { return gulp.src([ - icons.getPathToLogo('cypress-bw.png'), + cypressIcons.getPathToLogo('cypress-bw.png'), ]) .pipe(gulp.dest('dist/logos')) -}) +} + +const build = gulp.series( + clean, + gulp.parallel( + copySocketClient, + icons, + logos, + manifest, + background, + html, + css + ) +) + +const watchBuild = () => { + return gulp.watch('app/**/*', build) +} -gulp.task('build', gulp.series('clean', gulp.parallel( - 'icons', - 'logos', - 'manifest', - 'background', - 'html', - 'css', -))) +const watch = gulp.series(build, watchBuild) -gulp.task('watch', gulp.series('build'), (done) => { - gulp.watch('app/**/*', gulp.series('build')) - done() -}) +module.exports = { + build, + clean, + watch, +} diff --git a/packages/extension/package.json b/packages/extension/package.json index 690a048f6b68..f1041622750a 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -35,7 +35,7 @@ "gulp": "4.0.2", "gulp-clean": "0.4.0", "gulp-rename": "1.4.0", - "run-sequence": "1.2.2", + "rimraf": "2.6.3", "sinon": "7.3.2", "sinon-chai": "3.3.0", "vinyl-source-stream": "2.0.0" diff --git a/packages/https-proxy/test/integration/proxy_spec.coffee b/packages/https-proxy/test/integration/proxy_spec.coffee index b720a27b4a71..2f0a6d3bb11d 100644 --- a/packages/https-proxy/test/integration/proxy_spec.coffee +++ b/packages/https-proxy/test/integration/proxy_spec.coffee @@ -286,7 +286,7 @@ describe "Proxy", -> }) .then => throw new Error('should not succeed') - .catch { message: "Error: socket hang up" }, => + .catch { message: 'Error: Client network socket disconnected before secure TLS connection was established' }, => expect(createProxyConn).to.not.be.called expect(createSocket).to.be.calledWith({ port: @proxy._sniPort diff --git a/packages/network/test/unit/agent_spec.ts b/packages/network/test/unit/agent_spec.ts index 67c874aa55d5..d889c74096e7 100644 --- a/packages/network/test/unit/agent_spec.ts +++ b/packages/network/test/unit/agent_spec.ts @@ -298,7 +298,7 @@ describe('lib/agent', function () { .catch((e) => { expect(e.message).to.eq('Error: A connection to the upstream proxy could not be established: The upstream proxy closed the socket after connecting but before sending a response.') - return proxy.closeAsync() + return proxy.destroyAsync() }) }) }) diff --git a/packages/reporter/test/helper.js b/packages/reporter/test/helper.js index dac3d2cf9bc8..c574edbd326d 100644 --- a/packages/reporter/test/helper.js +++ b/packages/reporter/test/helper.js @@ -14,7 +14,7 @@ io.client.connect = sinon.stub().returns({ emit: () => {}, on: () => {} }) beforeEach(() => { driver.$ = sinon.stub().throws('$ called without being stubbed') - io.connect.throws('connect called without being stubbed') + io.connect = sinon.stub().throws('connect called without being stubbed') }) afterEach(() => { diff --git a/packages/server/__snapshots__/1_caught_uncaught_hook_errors_spec.coffee.js b/packages/server/__snapshots__/1_caught_uncaught_hook_errors_spec.coffee.js index ae3b6add2118..ebddd0bc50fc 100644 --- a/packages/server/__snapshots__/1_caught_uncaught_hook_errors_spec.coffee.js +++ b/packages/server/__snapshots__/1_caught_uncaught_hook_errors_spec.coffee.js @@ -56,7 +56,6 @@ Because this error occurred during a 'before each' hook we are skipping the rema at stack trace line at stack trace line at stack trace line - at stack trace line 2) s3a "before all" hook for "t8a": Error: s3a before hook failed diff --git a/packages/server/__snapshots__/1_commands_outside_of_test_spec.coffee.js b/packages/server/__snapshots__/1_commands_outside_of_test_spec.coffee.js index b236eeef1dec..9b2d59df4a33 100644 --- a/packages/server/__snapshots__/1_commands_outside_of_test_spec.coffee.js +++ b/packages/server/__snapshots__/1_commands_outside_of_test_spec.coffee.js @@ -226,3 +226,175 @@ We dynamically generated a new test to display this failure. ` + +exports['e2e commands outside of test [electron] fails on cy commands 1'] = ` + +==================================================================================================== + + (Run Starting) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Cypress: 1.2.3 │ + │ Browser: FooBrowser 88 │ + │ Specs: 1 found (commands_outside_of_test_spec.coffee) │ + │ Searched: cypress/integration/commands_outside_of_test_spec.coffee │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: commands_outside_of_test_spec.coffee... (1 of 1) + + + 1) An uncaught error was detected outside of a test + + 0 passing + 1 failing + + 1) An uncaught error was detected outside of a test: + Uncaught CypressError: Cannot call "cy.viewport()" outside a running test. + +This usually happens when you accidentally write commands outside an it(...) test. + +If that is the case, just move these commands inside an it(...) test. + +Check your test file for errors. + +https://on.cypress.io/cannot-execute-commands-outside-test + +This error originated from your test code, not from Cypress. + +When Cypress detects uncaught errors originating from your test code it will automatically fail the current test. + +Cypress could not associate this error to any specific test. + +We dynamically generated a new test to display this failure. + at stack trace line + at stack trace line + at stack trace line + at stack trace line + at stack trace line + at stack trace line + at stack trace line + + + + + (Results) + + ┌────────────────────────────────────────────────────┐ + │ Tests: 1 │ + │ Passing: 0 │ + │ Failing: 1 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 1 │ + │ Video: true │ + │ Duration: X seconds │ + │ Spec Ran: commands_outside_of_test_spec.coffee │ + └────────────────────────────────────────────────────┘ + + + (Screenshots) + + - /foo/bar/.projects/e2e/cypress/screenshots/commands_outside_of_test_spec.coffee/An uncaught error was detected outside of a test (failed).png (1280x720) + + + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) + + +==================================================================================================== + + (Run Finished) + + + Spec Tests Passing Failing Pending Skipped + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ ✖ commands_outside_of_test_spec.coffee XX:XX 1 - 1 - - │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + 1 of 1 failed (100%) XX:XX 1 - 1 - - + + +` + +exports['e2e commands outside of test [electron] fails on failing assertions 1'] = ` + +==================================================================================================== + + (Run Starting) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Cypress: 1.2.3 │ + │ Browser: FooBrowser 88 │ + │ Specs: 1 found (assertions_failing_outside_of_test_spec.coffee) │ + │ Searched: cypress/integration/assertions_failing_outside_of_test_spec.coffee │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: assertions_failing_outside_of_test_spec.coffee... (1 of 1) + + + 1) An uncaught error was detected outside of a test + + 0 passing + 1 failing + + 1) An uncaught error was detected outside of a test: + expected true to be false + +This error originated from your test code, not from Cypress. + +When Cypress detects uncaught errors originating from your test code it will automatically fail the current test. + +Cypress could not associate this error to any specific test. + +We dynamically generated a new test to display this failure. + AssertionError: expected true to be false + + + + + (Results) + + ┌──────────────────────────────────────────────────────────────┐ + │ Tests: 1 │ + │ Passing: 0 │ + │ Failing: 1 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 1 │ + │ Video: true │ + │ Duration: X seconds │ + │ Spec Ran: assertions_failing_outside_of_test_spec.coffee │ + └──────────────────────────────────────────────────────────────┘ + + + (Screenshots) + + - /foo/bar/.projects/e2e/cypress/screenshots/assertions_failing_outside_of_test_spec.coffee/An uncaught error was detected outside of a test (failed).png (1280x720) + + + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) + + +==================================================================================================== + + (Run Finished) + + + Spec Tests Passing Failing Pending Skipped + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ ✖ assertions_failing_outside_of_test_s… XX:XX 1 - 1 - - │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + 1 of 1 failed (100%) XX:XX 1 - 1 - - + + +` diff --git a/packages/server/__snapshots__/2_config_spec.coffee.js b/packages/server/__snapshots__/2_config_spec.coffee.js index 3f3b3f017474..e40409e731a2 100644 --- a/packages/server/__snapshots__/2_config_spec.coffee.js +++ b/packages/server/__snapshots__/2_config_spec.coffee.js @@ -106,7 +106,6 @@ exports['e2e config fails 1'] = ` at stack trace line at stack trace line at stack trace line - at stack trace line diff --git a/packages/server/__snapshots__/2_cookies_spec.coffee.js b/packages/server/__snapshots__/2_cookies_spec.coffee.js index a83a425d4de7..7c8873d24898 100644 --- a/packages/server/__snapshots__/2_cookies_spec.coffee.js +++ b/packages/server/__snapshots__/2_cookies_spec.coffee.js @@ -1,4 +1,75 @@ -exports['e2e cookies passes 1'] = ` +exports['e2e cookies passes in chrome 1'] = ` + +==================================================================================================== + + (Run Starting) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Cypress: 1.2.3 │ + │ Browser: FooBrowser 88 │ + │ Specs: 1 found (cookies_spec.coffee) │ + │ Searched: cypress/integration/cookies_spec.coffee │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: cookies_spec.coffee... (1 of 1) + +Warning: Cypress can only record videos when using the built in 'electron' browser. + +You have set the browser to: 'chrome' + +A video will not be recorded when using this browser. + + + cookies + with whitelist + ✓ can get all cookies + ✓ resets cookies between tests correctly + ✓ should be only two left now + ✓ handles undefined cookies + without whitelist + ✓ sends cookies to localhost:2121 + ✓ handles expired cookies secure + ✓ issue: #224 sets expired cookies between redirects + ✓ issue: #1321 failing to set or parse cookie + ✓ issue: #2724 does not fail on invalid cookies + + + 9 passing + + + (Results) + + ┌───────────────────────────────────┐ + │ Tests: 9 │ + │ Passing: 9 │ + │ Failing: 0 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 0 │ + │ Video: false │ + │ Duration: X seconds │ + │ Spec Ran: cookies_spec.coffee │ + └───────────────────────────────────┘ + + +==================================================================================================== + + (Run Finished) + + + Spec Tests Passing Failing Pending Skipped + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ ✔ cookies_spec.coffee XX:XX 9 9 - - - │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + All specs passed! XX:XX 9 9 - - - + + +` + +exports['e2e cookies passes in electron 1'] = ` ==================================================================================================== diff --git a/packages/server/__snapshots__/2_form_submissions_spec.coffee.js b/packages/server/__snapshots__/2_form_submissions_spec.coffee.js index a7d21c781fc1..bffcf83f9916 100644 --- a/packages/server/__snapshots__/2_form_submissions_spec.coffee.js +++ b/packages/server/__snapshots__/2_form_submissions_spec.coffee.js @@ -176,7 +176,6 @@ exports['e2e forms submissions with jquery XHR POST failing 1'] = ` at stack trace line at stack trace line at stack trace line - at stack trace line diff --git a/packages/server/__snapshots__/3_issue_173_spec.coffee.js b/packages/server/__snapshots__/3_issue_173_spec.coffee.js index 5815ca18250d..a2d83f60c984 100644 --- a/packages/server/__snapshots__/3_issue_173_spec.coffee.js +++ b/packages/server/__snapshots__/3_issue_173_spec.coffee.js @@ -38,7 +38,6 @@ exports['e2e issue 173 failing 1'] = ` at stack trace line at stack trace line at stack trace line - at stack trace line diff --git a/packages/server/__snapshots__/3_plugins_spec.coffee.js b/packages/server/__snapshots__/3_plugins_spec.coffee.js index 163ee4f41137..170dc941d580 100644 --- a/packages/server/__snapshots__/3_plugins_spec.coffee.js +++ b/packages/server/__snapshots__/3_plugins_spec.coffee.js @@ -22,7 +22,6 @@ Error: Async error from plugins file at stack trace line at stack trace line at stack trace line - at stack trace line (Results) diff --git a/packages/server/__snapshots__/4_promises_spec.coffee.js b/packages/server/__snapshots__/4_promises_spec.coffee.js index 65c8945760ab..7fbf62e89be3 100644 --- a/packages/server/__snapshots__/4_promises_spec.coffee.js +++ b/packages/server/__snapshots__/4_promises_spec.coffee.js @@ -30,7 +30,6 @@ exports['e2e promises failing1 1'] = ` 2) catches promise errors and calls done with err even when async: Error: foo at stack trace line - at stack trace line diff --git a/packages/server/__snapshots__/4_request_spec.coffee.js b/packages/server/__snapshots__/4_request_spec.coffee.js index 62723ff4db6d..359e88c21876 100644 --- a/packages/server/__snapshots__/4_request_spec.coffee.js +++ b/packages/server/__snapshots__/4_request_spec.coffee.js @@ -1,75 +1,3 @@ -exports['e2e requests passes 1'] = ` - -==================================================================================================== - - (Run Starting) - - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ Cypress: 1.2.3 │ - │ Browser: FooBrowser 88 │ - │ Specs: 1 found (request_spec.coffee) │ - │ Searched: cypress/integration/request_spec.coffee │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - - -──────────────────────────────────────────────────────────────────────────────────────────────────── - - Running: request_spec.coffee... (1 of 1) - - - redirects + requests - ✓ gets and sets cookies from cy.request - ✓ visits idempotant - ✓ automatically follows redirects - ✓ can turn off automatically following redirects - ✓ follows all redirects even when they change methods - ✓ can submit json body - ✓ can submit form url encoded body - ✓ can send qs query params - ✓ passes even on non 2xx or 3xx status code - ✓ sets Accept header to */* by default - ✓ can override the accept header - ✓ issue #375: does not duplicate request cookies on 302 redirect - - - 12 passing - - - (Results) - - ┌───────────────────────────────────┐ - │ Tests: 12 │ - │ Passing: 12 │ - │ Failing: 0 │ - │ Pending: 0 │ - │ Skipped: 0 │ - │ Screenshots: 0 │ - │ Video: true │ - │ Duration: X seconds │ - │ Spec Ran: request_spec.coffee │ - └───────────────────────────────────┘ - - - (Video) - - - Started processing: Compressing to 32 CRF - - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) - - -==================================================================================================== - - (Run Finished) - - - Spec Tests Passing Failing Pending Skipped - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ ✔ request_spec.coffee XX:XX 12 12 - - - │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - All specs passed! XX:XX 12 12 - - - - - -` - exports['e2e requests fails when network immediately fails 1'] = ` ==================================================================================================== @@ -136,9 +64,6 @@ RequestError: Error: connect ECONNREFUSED 127.0.0.1:16795 at stack trace line at stack trace line at stack trace line - at stack trace line - at stack trace line - at stack trace line at stack trace line at stack trace line @@ -155,7 +80,6 @@ RequestError: Error: connect ECONNREFUSED 127.0.0.1:16795 at stack trace line at stack trace line at stack trace line - at stack trace line @@ -279,7 +203,6 @@ Body: Service Unavailable at stack trace line at stack trace line at stack trace line - at stack trace line @@ -323,3 +246,147 @@ Body: Service Unavailable ` + +exports['e2e requests passes in electron 1'] = ` + +==================================================================================================== + + (Run Starting) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Cypress: 1.2.3 │ + │ Browser: FooBrowser 88 │ + │ Specs: 1 found (request_spec.coffee) │ + │ Searched: cypress/integration/request_spec.coffee │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: request_spec.coffee... (1 of 1) + + + redirects + requests + ✓ gets and sets cookies from cy.request + ✓ visits idempotant + ✓ automatically follows redirects + ✓ can turn off automatically following redirects + ✓ follows all redirects even when they change methods + ✓ can submit json body + ✓ can submit form url encoded body + ✓ can send qs query params + ✓ passes even on non 2xx or 3xx status code + ✓ sets Accept header to */* by default + ✓ can override the accept header + ✓ issue #375: does not duplicate request cookies on 302 redirect + + + 12 passing + + + (Results) + + ┌───────────────────────────────────┐ + │ Tests: 12 │ + │ Passing: 12 │ + │ Failing: 0 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 0 │ + │ Video: true │ + │ Duration: X seconds │ + │ Spec Ran: request_spec.coffee │ + └───────────────────────────────────┘ + + + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) + + +==================================================================================================== + + (Run Finished) + + + Spec Tests Passing Failing Pending Skipped + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ ✔ request_spec.coffee XX:XX 12 12 - - - │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + All specs passed! XX:XX 12 12 - - - + + +` + +exports['e2e requests passes in chrome 1'] = ` + +==================================================================================================== + + (Run Starting) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Cypress: 1.2.3 │ + │ Browser: FooBrowser 88 │ + │ Specs: 1 found (request_spec.coffee) │ + │ Searched: cypress/integration/request_spec.coffee │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: request_spec.coffee... (1 of 1) + +Warning: Cypress can only record videos when using the built in 'electron' browser. + +You have set the browser to: 'chrome' + +A video will not be recorded when using this browser. + + + redirects + requests + ✓ gets and sets cookies from cy.request + ✓ visits idempotant + ✓ automatically follows redirects + ✓ can turn off automatically following redirects + ✓ follows all redirects even when they change methods + ✓ can submit json body + ✓ can submit form url encoded body + ✓ can send qs query params + ✓ passes even on non 2xx or 3xx status code + ✓ sets Accept header to */* by default + ✓ can override the accept header + ✓ issue #375: does not duplicate request cookies on 302 redirect + + + 12 passing + + + (Results) + + ┌───────────────────────────────────┐ + │ Tests: 12 │ + │ Passing: 12 │ + │ Failing: 0 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 0 │ + │ Video: false │ + │ Duration: X seconds │ + │ Spec Ran: request_spec.coffee │ + └───────────────────────────────────┘ + + +==================================================================================================== + + (Run Finished) + + + Spec Tests Passing Failing Pending Skipped + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ ✔ request_spec.coffee XX:XX 12 12 - - - │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + All specs passed! XX:XX 12 12 - - - + + +` diff --git a/packages/server/__snapshots__/4_return_value_spec.coffee.js b/packages/server/__snapshots__/4_return_value_spec.coffee.js index 9db8da1481dd..711e65c28f9b 100644 --- a/packages/server/__snapshots__/4_return_value_spec.coffee.js +++ b/packages/server/__snapshots__/4_return_value_spec.coffee.js @@ -83,7 +83,6 @@ https://on.cypress.io/returning-value-and-commands-in-custom-command at stack trace line at stack trace line at stack trace line - at stack trace line diff --git a/packages/server/__snapshots__/5_spec_isolation_spec.coffee.js b/packages/server/__snapshots__/5_spec_isolation_spec.coffee.js index 2519db703d25..0ece8d0d9714 100644 --- a/packages/server/__snapshots__/5_spec_isolation_spec.coffee.js +++ b/packages/server/__snapshots__/5_spec_isolation_spec.coffee.js @@ -39,7 +39,7 @@ exports['e2e spec_isolation failing 1'] = { "title": [ "\"before each\" hook" ], - "body": "function () {\n throw new Error(\"fail1\");\n }" + "body": "function() {\n throw new Error(\"fail1\");\n }" }, { "hookId": "h2", @@ -47,7 +47,7 @@ exports['e2e spec_isolation failing 1'] = { "title": [ "\"after each\" hook" ], - "body": "function () {\n throw new Error(\"fail2\");\n }" + "body": "function() {\n throw new Error(\"fail2\");\n }" }, { "hookId": "h3", @@ -55,7 +55,7 @@ exports['e2e spec_isolation failing 1'] = { "title": [ "\"after all\" hook" ], - "body": "function () {\n throw new Error(\"fail3\");\n }" + "body": "function() {\n throw new Error(\"fail3\");\n }" } ], "tests": [ @@ -67,7 +67,7 @@ exports['e2e spec_isolation failing 1'] = { "never gets here" ], "state": "failed", - "body": "function () {}", + "body": "function() {}", "stack": "Error: fail1\n\nBecause this error occurred during a 'before each' hook we are skipping the remaining tests in the current suite: 'beforeEach hooks'\n at stack trace line", "error": "fail1\n\nBecause this error occurred during a 'before each' hook we are skipping the remaining tests in the current suite: 'beforeEach hooks'", "timings": { @@ -110,7 +110,7 @@ exports['e2e spec_isolation failing 1'] = { "runs this" ], "state": "failed", - "body": "function () {}", + "body": "function() {}", "stack": "Error: fail2\n\nBecause this error occurred during a 'after each' hook we are skipping the remaining tests in the current suite: 'afterEach hooks'\n at stack trace line", "error": "fail2\n\nBecause this error occurred during a 'after each' hook we are skipping the remaining tests in the current suite: 'afterEach hooks'", "timings": { @@ -140,7 +140,7 @@ exports['e2e spec_isolation failing 1'] = { "does not run this" ], "state": "skipped", - "body": "function () {}", + "body": "function() {}", "stack": null, "error": null, "timings": null, @@ -157,7 +157,7 @@ exports['e2e spec_isolation failing 1'] = { "runs this" ], "state": "passed", - "body": "function () {}", + "body": "function() {}", "stack": null, "error": null, "timings": { @@ -180,7 +180,7 @@ exports['e2e spec_isolation failing 1'] = { "fails on this" ], "state": "failed", - "body": "function () {}", + "body": "function() {}", "stack": "Error: fail3\n\nBecause this error occurred during a 'after all' hook we are skipping the remaining tests in the current suite: 'after hooks'\n at stack trace line", "error": "fail3\n\nBecause this error occurred during a 'after all' hook we are skipping the remaining tests in the current suite: 'after hooks'", "timings": { @@ -273,8 +273,8 @@ exports['e2e spec_isolation failing 1'] = { "fails1" ], "state": "failed", - "body": "function () {\n return cy.wrap(true, {\n timeout: 100\n }).should(\"be.false\");\n }", - "stack": "CypressError: Timed out retrying: expected true to be false\n at stack trace line\n at stack trace line\n at stack trace line\n at stack trace line\n at stack trace line\n at stack trace line\n at stack trace line\n at stack trace line\n at stack trace line\n at stack trace line\n at stack trace line\n at stack trace line\n at stack trace line\n at stack trace line", + "body": "function() {\n return cy.wrap(true, {\n timeout: 100\n }).should(\"be.false\");\n }", + "stack": "CypressError: Timed out retrying: expected true to be false\n at stack trace line\n at stack trace line\n at stack trace line\n at stack trace line\n at stack trace line\n at stack trace line\n at stack trace line\n at stack trace line\n at stack trace line\n at stack trace line\n at stack trace line\n at stack trace line\n at stack trace line", "error": "Timed out retrying: expected true to be false", "timings": { "lifecycle": 100, @@ -295,7 +295,7 @@ exports['e2e spec_isolation failing 1'] = { "fails2" ], "state": "failed", - "body": "function () {\n throw new Error(\"fails2\");\n }", + "body": "function() {\n throw new Error(\"fails2\");\n }", "stack": "Error: fails2\n at stack trace line", "error": "fails2", "timings": { @@ -370,7 +370,7 @@ exports['e2e spec_isolation failing 1'] = { "title": [ "\"before all\" hook" ], - "body": "function () {\n return cy.wait(100);\n }" + "body": "function() {\n return cy.wait(100);\n }" }, { "hookId": "h2", @@ -378,7 +378,7 @@ exports['e2e spec_isolation failing 1'] = { "title": [ "\"before each\" hook" ], - "body": "function () {\n return cy.wait(200);\n }" + "body": "function() {\n return cy.wait(200);\n }" }, { "hookId": "h3", @@ -386,7 +386,7 @@ exports['e2e spec_isolation failing 1'] = { "title": [ "\"after each\" hook" ], - "body": "function () {\n return cy.wait(200);\n }" + "body": "function() {\n return cy.wait(200);\n }" }, { "hookId": "h4", @@ -394,7 +394,7 @@ exports['e2e spec_isolation failing 1'] = { "title": [ "\"after all\" hook" ], - "body": "function () {\n return cy.wait(100);\n }" + "body": "function() {\n return cy.wait(100);\n }" } ], "tests": [ @@ -405,7 +405,7 @@ exports['e2e spec_isolation failing 1'] = { "t1" ], "state": "passed", - "body": "function () {\n return cy.wrap(\"t1\").should(\"eq\", \"t1\");\n }", + "body": "function() {\n return cy.wrap(\"t1\").should(\"eq\", \"t1\");\n }", "stack": null, "error": null, "timings": { @@ -448,7 +448,7 @@ exports['e2e spec_isolation failing 1'] = { "t2" ], "state": "passed", - "body": "function () {\n return cy.wrap(\"t2\").should(\"eq\", \"t2\");\n }", + "body": "function() {\n return cy.wrap(\"t2\").should(\"eq\", \"t2\");\n }", "stack": null, "error": null, "timings": { @@ -484,7 +484,7 @@ exports['e2e spec_isolation failing 1'] = { "t3" ], "state": "passed", - "body": "function () {\n return cy.wrap(\"t3\").should(\"eq\", \"t3\");\n }", + "body": "function() {\n return cy.wrap(\"t3\").should(\"eq\", \"t3\");\n }", "stack": null, "error": null, "timings": { @@ -561,7 +561,7 @@ exports['e2e spec_isolation failing 1'] = { "title": [ "\"before each\" hook" ], - "body": "function () {\n return cy.wait(1000);\n }" + "body": "function() {\n return cy.wait(1000);\n }" } ], "tests": [ @@ -572,7 +572,7 @@ exports['e2e spec_isolation failing 1'] = { "passes" ], "state": "passed", - "body": "function () {\n return cy.wrap(true).should(\"be.true\");\n }", + "body": "function() {\n return cy.wrap(true).should(\"be.true\");\n }", "stack": null, "error": null, "timings": { diff --git a/packages/server/__snapshots__/5_stdout_spec.coffee.js b/packages/server/__snapshots__/5_stdout_spec.coffee.js index 3cbaab1866ac..40e9f3daf735 100644 --- a/packages/server/__snapshots__/5_stdout_spec.coffee.js +++ b/packages/server/__snapshots__/5_stdout_spec.coffee.js @@ -62,7 +62,6 @@ Because this error occurred during a 'before each' hook we are skipping the rema at stack trace line at stack trace line at stack trace line - at stack trace line 3) stdout_failing_spec passing hook is failing: CypressError: cy.visit() failed trying to load: @@ -90,7 +89,6 @@ The internal Cypress web server responded with: at stack trace line at stack trace line at stack trace line - at stack trace line diff --git a/packages/server/__snapshots__/5_subdomain_spec.coffee.js b/packages/server/__snapshots__/5_subdomain_spec.coffee.js index d7b5294d2a53..d6b9e0fd261e 100644 --- a/packages/server/__snapshots__/5_subdomain_spec.coffee.js +++ b/packages/server/__snapshots__/5_subdomain_spec.coffee.js @@ -1,4 +1,4 @@ -exports['e2e subdomain passes 1'] = ` +exports['e2e subdomain passes in electron 1'] = ` ==================================================================================================== @@ -21,7 +21,7 @@ exports['e2e subdomain passes 1'] = ` ✓ can swap to help.foobar.com:2292 ✓ can directly visit a subdomain in another test ✓ issue: #207: does not duplicate or hostOnly cookies as a domain cookie - ✓ corrects sets domain based cookies + ✓ correctly sets domain based cookies - issue #362: do not set domain based (non hostOnly) cookies by default - sets a hostOnly cookie by default ✓ issue #361: incorrect cookie synchronization between cy.request redirects @@ -67,3 +67,73 @@ exports['e2e subdomain passes 1'] = ` ` + +exports['e2e subdomain passes in chrome 1'] = ` + +==================================================================================================== + + (Run Starting) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Cypress: 1.2.3 │ + │ Browser: FooBrowser 88 │ + │ Specs: 1 found (subdomain_spec.coffee) │ + │ Searched: cypress/integration/subdomain_spec.coffee │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: subdomain_spec.coffee... (1 of 1) + +Warning: Cypress can only record videos when using the built in 'electron' browser. + +You have set the browser to: 'chrome' + +A video will not be recorded when using this browser. + + + subdomains + ✓ can swap to help.foobar.com:2292 + ✓ can directly visit a subdomain in another test + ✓ issue: #207: does not duplicate or hostOnly cookies as a domain cookie + ✓ correctly sets domain based cookies + - issue #362: do not set domain based (non hostOnly) cookies by default + - sets a hostOnly cookie by default + ✓ issue #361: incorrect cookie synchronization between cy.request redirects + ✓ issue #362: incorrect cookie synchronization between cy.visit redirects + ✓ issue #600 can visit between nested subdomains + + + 7 passing + 2 pending + + + (Results) + + ┌─────────────────────────────────────┐ + │ Tests: 9 │ + │ Passing: 7 │ + │ Failing: 0 │ + │ Pending: 2 │ + │ Skipped: 0 │ + │ Screenshots: 0 │ + │ Video: false │ + │ Duration: X seconds │ + │ Spec Ran: subdomain_spec.coffee │ + └─────────────────────────────────────┘ + + +==================================================================================================== + + (Run Finished) + + + Spec Tests Passing Failing Pending Skipped + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ ✔ subdomain_spec.coffee XX:XX 9 7 - 2 - │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + All specs passed! XX:XX 9 7 - 2 - + + +` diff --git a/packages/server/__snapshots__/5_task_not_registered_spec.coffee.js b/packages/server/__snapshots__/5_task_not_registered_spec.coffee.js index f9135f634900..aec838fb7c37 100644 --- a/packages/server/__snapshots__/5_task_not_registered_spec.coffee.js +++ b/packages/server/__snapshots__/5_task_not_registered_spec.coffee.js @@ -43,7 +43,6 @@ https://on.cypress.io/api/task at stack trace line at stack trace line at stack trace line - at stack trace line diff --git a/packages/server/__snapshots__/6_task_spec.coffee.js b/packages/server/__snapshots__/6_task_spec.coffee.js index 87373537a423..3392b42d1271 100644 --- a/packages/server/__snapshots__/6_task_spec.coffee.js +++ b/packages/server/__snapshots__/6_task_spec.coffee.js @@ -90,7 +90,7 @@ The task 'returns:undefined' returned undefined. You must return a promise, a va The task handler was: -returns:undefined() {} +'returns:undefined' () {} Fix this in your plugins file here: /foo/bar/.projects/e2e/cypress/plugins/index.js @@ -108,7 +108,6 @@ https://on.cypress.io/api/task at stack trace line at stack trace line at stack trace line - at stack trace line 2) includes stack trace in error: CypressError: cy.task('errors') failed with the following error: @@ -127,9 +126,6 @@ https://on.cypress.io/api/task at stack trace line at stack trace line at stack trace line - at stack trace line - at stack trace line - at stack trace line at stack trace line at stack trace line @@ -144,7 +140,6 @@ https://on.cypress.io/api/task at stack trace line at stack trace line at stack trace line - at stack trace line diff --git a/packages/server/__snapshots__/6_visit_spec.coffee.js b/packages/server/__snapshots__/6_visit_spec.coffee.js index bded52377e3f..5a3c52e5927d 100644 --- a/packages/server/__snapshots__/6_visit_spec.coffee.js +++ b/packages/server/__snapshots__/6_visit_spec.coffee.js @@ -119,8 +119,6 @@ Common situations why this would fail: The stack trace for this error is: Error: connect ECONNREFUSED 127.0.0.1:16795 - at stack trace line - at stack trace line at stack trace line at stack trace line @@ -137,7 +135,6 @@ Error: connect ECONNREFUSED 127.0.0.1:16795 at stack trace line at stack trace line at stack trace line - at stack trace line @@ -234,7 +231,6 @@ If you do not want status codes to cause failures pass the option: 'failOnStatus at stack trace line at stack trace line at stack trace line - at stack trace line @@ -331,7 +327,6 @@ The internal Cypress web server responded with: at stack trace line at stack trace line at stack trace line - at stack trace line @@ -430,7 +425,6 @@ cy.request() will automatically get and set cookies and enable you to parse resp at stack trace line at stack trace line at stack trace line - at stack trace line @@ -527,7 +521,6 @@ When this 'load' event occurs, Cypress will continue running commands. at stack trace line at stack trace line at stack trace line - at stack trace line 2) when visit times out fails timeout exceeds timeout option: CypressError: Timed out after waiting '500ms' for your remote page to load. @@ -554,7 +547,6 @@ When this 'load' event occurs, Cypress will continue running commands. at stack trace line at stack trace line at stack trace line - at stack trace line @@ -656,9 +648,6 @@ Error: ESOCKETTIMEDOUT at stack trace line at stack trace line at stack trace line - at stack trace line - at stack trace line - at stack trace line at stack trace line at stack trace line @@ -675,7 +664,6 @@ Error: ESOCKETTIMEDOUT at stack trace line at stack trace line at stack trace line - at stack trace line 2) response timeouts result in an error handles no response errors when not initially visiting: CypressError: cy.visit() failed trying to load: @@ -705,9 +693,6 @@ Error: ESOCKETTIMEDOUT at stack trace line at stack trace line at stack trace line - at stack trace line - at stack trace line - at stack trace line at stack trace line at stack trace line @@ -724,7 +709,6 @@ Error: ESOCKETTIMEDOUT at stack trace line at stack trace line at stack trace line - at stack trace line 3) response timeouts result in an error fails after reducing the responseTimeout option: CypressError: cy.visit() failed trying to load: @@ -754,9 +738,6 @@ Error: ESOCKETTIMEDOUT at stack trace line at stack trace line at stack trace line - at stack trace line - at stack trace line - at stack trace line at stack trace line at stack trace line @@ -773,7 +754,6 @@ Error: ESOCKETTIMEDOUT at stack trace line at stack trace line at stack trace line - at stack trace line diff --git a/packages/server/__snapshots__/8_reporters_spec.coffee.js b/packages/server/__snapshots__/8_reporters_spec.coffee.js index 083b5ec41277..65b84dfe8b43 100644 --- a/packages/server/__snapshots__/8_reporters_spec.coffee.js +++ b/packages/server/__snapshots__/8_reporters_spec.coffee.js @@ -9,6 +9,14 @@ We searched for the reporter in these paths: The error we received was: Cannot find module '/foo/bar/.projects/e2e/node_modules/module-does-not-exist' +Require stack: +- lib/reporter.coffee +- lib/project.coffee +- lib/modes/run.js +- lib/modes/index.coffee +- lib/cypress.coffee +- index.js +- Learn more at https://on.cypress.io/reporters @@ -690,9 +698,6 @@ Error: this reporter threw an error at stack trace line at stack trace line at stack trace line - at stack trace line - at stack trace line - at stack trace line Learn more at https://on.cypress.io/reporters diff --git a/packages/server/__snapshots__/run_plugins_spec.coffee.js b/packages/server/__snapshots__/run_plugins_spec.coffee.js index 091c350559b2..1e473b41d3c0 100644 --- a/packages/server/__snapshots__/run_plugins_spec.coffee.js +++ b/packages/server/__snapshots__/run_plugins_spec.coffee.js @@ -1,62 +1,9 @@ exports['lib/plugins/child/run_plugins sends error message if pluginsFile is missing 1'] = ` Error: Cannot find module '/does/not/exist.coffee' - at Function.Module._resolveFilename module.js - at Module._load module.js - at Function.hookedLoader [as _load] mockery.js - at Module.require module.js - at require module.js - at module.exports run_plugins.js - at Context. run_plugins_spec.coffee - at callFn runnable.js - at Test.Runnable.run runnable.js - at Runner.runTest runner.js - at runner.js - at next runner.js - at runner.js - at next runner.js - at runner.js - at done runnable.js - at callFn runnable.js - at Hook.Runnable.run runnable.js - at next runner.js - at Immediate. runner.js - at runCallback timers.js - at tryOnImmediate timers.js - at processImmediate [as _immediateCallback] timers.js - ` exports['lib/plugins/child/run_plugins sends error message if requiring pluginsFile errors 1'] = ` Error: error thrown by pluginsFile - at Object. throws_error.coffee - at Object. throws_error.coffee - at Module._compile module.js - at Object.loadFile register.js - at Module.load register.js - at tryModuleLoad module.js - at Module._load module.js - at Function.hookedLoader [as _load] mockery.js - at Module.require module.js - at require module.js - at module.exports run_plugins.js - at Context. run_plugins_spec.coffee - at callFn runnable.js - at Test.Runnable.run runnable.js - at Runner.runTest runner.js - at runner.js - at next runner.js - at runner.js - at next runner.js - at runner.js - at done runnable.js - at callFn runnable.js - at Hook.Runnable.run runnable.js - at next runner.js - at Immediate. runner.js - at runCallback timers.js - at tryOnImmediate timers.js - at processImmediate [as _immediateCallback] timers.js - ` exports['lib/plugins/child/run_plugins sends error message if pluginsFile has syntax error 1'] = ` diff --git a/packages/server/index.js b/packages/server/index.js index 67560c4de013..559058b7438f 100644 --- a/packages/server/index.js +++ b/packages/server/index.js @@ -1,11 +1,6 @@ // override tty if we're being forced to require('./lib/util/tty').override() -// fix for Node v8.9.3 not resolving localhost if system is offline -// this can be removed as soon as we pass Node v8.10.0 -// https://github.com/cypress-io/cypress/issues/4763 -require('node-offline-localhost').ifOffline() - if (process.env.CY_NET_PROFILE && process.env.CYPRESS_ENV) { const netProfiler = require('./lib/util/net_profiler')() @@ -23,4 +18,8 @@ require('@packages/coffee/register') require && require.extensions && delete require.extensions['.litcoffee'] require && require.extensions && delete require.extensions['.coffee.md'] +// warn when deprecated callback apis are used in electron +// https://github.com/electron/electron/blob/master/docs/api/process.md#processenablepromiseapis +process.enablePromiseAPIs = process.env.CYPRESS_ENV !== 'production' + module.exports = require('./lib/cypress').start(process.argv) diff --git a/packages/server/lib/automation/cookies.coffee b/packages/server/lib/automation/cookies.coffee index 1ffcc43a94de..f9d4233dd28c 100644 --- a/packages/server/lib/automation/cookies.coffee +++ b/packages/server/lib/automation/cookies.coffee @@ -5,25 +5,22 @@ debug = require("debug")("cypress:server:cookies") ## match the w3c webdriver spec on return cookies ## https://w3c.github.io/webdriver/webdriver-spec.html#cookies -COOKIE_PROPERTIES = "name value path domain secure httpOnly expiry".split(" ") +COOKIE_PROPERTIES = "name value path domain secure httpOnly expiry hostOnly".split(" ") -normalizeCookies = (cookies, includeHostOnly) -> - _.map cookies, (c) -> - normalizeCookieProps(c, includeHostOnly) +normalizeCookies = (cookies) -> + _.map cookies, normalizeCookieProps -normalizeCookieProps = (props, includeHostOnly) -> +normalizeCookieProps = (props) -> return props if not props ## pick off only these specific cookie properties ## only if they are defined - cookie = _.chain(props, COOKIE_PROPERTIES) + cookie = _.chain(props) .pick(COOKIE_PROPERTIES) .omitBy(_.isUndefined) + .omitBy(_.isNull) .value() - if includeHostOnly - cookie.hostOnly = props.hostOnly - ## when sending cookie props we need to convert ## expiry to expirationDate ## ... @@ -40,6 +37,19 @@ normalizeCookieProps = (props, includeHostOnly) -> cookie +normalizeGetCookies = (cookies) -> + _.chain(cookies) + .map(normalizeGetCookieProps) + ## sort in order of expiration date, ascending + .sortBy(_.partialRight(_.get, 'expiry', Number.MAX_SAFE_INTEGER)) + .value() + +normalizeGetCookieProps = (props) -> + return props if not props + + cookie = normalizeCookieProps(props) + _.omit(cookie, 'hostOnly') + cookies = (cyNamespace, cookieNamespace) -> isNamespaced = (cookie) -> name = cookie and cookie.name @@ -51,29 +61,12 @@ cookies = (cyNamespace, cookieNamespace) -> name.startsWith(cyNamespace) or name is cookieNamespace return { - # normalize: (message, data, automate) -> - # invoke = (fn) => - # fn.call(@, data, automate) - - # fn = switch message - # when "get:cookies" then @getCookies - # when "get:cookie" then @getCookie - # when "set:cookie" then @setCookie - # when "clear:cookie" then @clearCookie - # when "clear:cookies" then @clearCookies - - # invoke(fn) - getCookies: (data, automate) -> - { includeHostOnly } = data - - delete data.includeHostOnly - debug("getting:cookies %o", data) automate(data) .then (cookies) -> - cookies = normalizeCookies(cookies, includeHostOnly) + cookies = normalizeGetCookies(cookies) cookies = _.reject(cookies, isNamespaced) debug("received get:cookies %o", cookies) @@ -88,7 +81,7 @@ cookies = (cyNamespace, cookieNamespace) -> if isNamespaced(cookie) throw new Error("Sorry, you cannot get a Cypress namespaced cookie.") else - cookie = normalizeCookieProps(cookie) + cookie = normalizeGetCookieProps(cookie) debug("received get:cookie %o", cookie) @@ -104,25 +97,11 @@ cookies = (cyNamespace, cookieNamespace) -> ## unless we already have a URL cookie.url = data.url ? extension.getCookieUrl(data) - ## https://github.com/SalesforceEng/tough-cookie#setcookiecookieorstring-currenturl-options-cberrcookie - ## a host only cookie is when domain was not explictly - ## set in the Set-Cookie header and instead was implied. - ## when this is the case we need to remove the domain - ## property else our cookie will incorrectly be set - ## as a domain cookie - ## - ## hostOnly=true means no domain= property was set, so - ## this cookie is specific to being bound to the exact domain - ## hostOnly=false means that a domain= property was set, so - ## this cookie has been relaxed to apply to multiple subdomains - if data.hostOnly - cookie = _.omit(cookie, "domain") - debug("set:cookie %o", cookie) automate(cookie) .then (cookie) -> - cookie = normalizeCookieProps(cookie) + cookie = normalizeGetCookieProps(cookie) debug("received set:cookie %o", cookie) @@ -145,10 +124,10 @@ cookies = (cyNamespace, cookieNamespace) -> clearCookies: (data, automate) -> cookies = _.reject(normalizeCookies(data), isNamespaced) - debug("clear:cookies %o", data) + debug("clear:cookies %o", cookies) clear = (cookie) -> - automate("clear:cookie", { name: cookie.name }) + automate("clear:cookie", { name: cookie.name, domain: cookie.domain }) .then(normalizeCookieProps) Promise.map(cookies, clear) diff --git a/packages/server/lib/browsers/electron.coffee b/packages/server/lib/browsers/electron.coffee index 4c477532daec..338a6fb1c7d2 100644 --- a/packages/server/lib/browsers/electron.coffee +++ b/packages/server/lib/browsers/electron.coffee @@ -1,10 +1,13 @@ _ = require("lodash") EE = require("events") +net = require("net") Promise = require("bluebird") +tough = require("tough-cookie") debug = require("debug")("cypress:server:browsers:electron") menu = require("../gui/menu") Windows = require("../gui/windows") appData = require("../util/app_data") +cors = require("../util/cors") plugins = require("../plugins") savedState = require("../saved_state") profileCleaner = require("../util/profile_cleaner") @@ -19,6 +22,85 @@ tryToCall = (win, method) -> catch err debug("got error calling window method:", err.stack) +getAutomation = (win) -> + invokeViaDebugger = (message, data) -> + tryToCall win, -> + win.webContents.debugger.sendCommand(message, data) + + normalizeGetCookieProps = (cookie) -> + if cookie.expires == -1 + delete cookie.expires + cookie.expirationDate = cookie.expires + delete cookie.expires + return cookie + + normalizeGetCookies = (cookies) -> + _.map(cookies, normalizeGetCookieProps) + + normalizeSetCookieProps = (cookie) -> + cookie.name or= "" ## name can't be undefined/null + cookie.value or= "" ## ditto + cookie.expires = cookie.expirationDate + + ## see Chromium's GetCookieDomainWithString for the logic here: + ## https://cs.chromium.org/chromium/src/net/cookies/cookie_util.cc?l=120&rcl=1b63a4b7ba498e3f6d25ec5d33053d7bc8aa4404 + if !cookie.hostOnly and cookie.domain[0] != '.' + parsedDomain = cors.parseDomain(cookie.domain) + ## not a top-level domain (localhost, ...) or IP address + if parsedDomain && parsedDomain.tld != cookie.domain + cookie.domain = ".#{cookie.domain}" + + delete cookie.hostOnly + delete cookie.expirationDate + return cookie + + getAllCookies = (data) -> + invokeViaDebugger("Network.getAllCookies") + .then (result) -> + normalizeGetCookies(result.cookies) + .filter (cookie) -> + _.every([ + !data.domain || tough.domainMatch(cookie.domain, data.domain) + !data.path || tough.pathMatch(cookie.path, data.path) + !data.name || data.name == cookie.name + ]) + + getCookiesByUrl = (url) -> + invokeViaDebugger("Network.getCookies", { urls: [ url ] }) + .then (result) -> + normalizeGetCookies(result.cookies) + + getCookie = (data) -> + getAllCookies(data).then _.partialRight(_.get, 0, null) + + return { + onRequest: (message, data) -> + switch message + when "get:cookies" + if data?.url + return getCookiesByUrl(data.url) + getAllCookies(data) + when "get:cookie" + getCookie(data) + when "set:cookie" + setCookie = normalizeSetCookieProps(data) + invokeViaDebugger("Network.setCookie", setCookie).then -> + getCookie(data) + when "clear:cookie" + getCookie(data) ## so we can resolve with the value of the removed cookie + .then (cookieToBeCleared) -> + invokeViaDebugger("Network.deleteCookies", data) + .then -> + cookieToBeCleared + when "is:automation:client:connected" + true + when "take:screenshot" + tryToCall(win, 'capturePage') + .then _.partialRight(_.invoke, 'toDataURL') + else + throw new Error("No automation handler registered for: '#{message}'") + } + module.exports = { _defaultOptions: (projectRoot, state, options) -> _this = @ @@ -56,6 +138,8 @@ module.exports = { _.defaultsDeep({}, options, defaults) + _getAutomation: getAutomation + _render: (url, projectRoot, options = {}) -> win = Windows.create(projectRoot, options) @@ -87,11 +171,9 @@ module.exports = { if options.show menu.set({withDevTools: true}) - Promise - .try => - if options.show is false - @_attachDebugger(win.webContents) - + Promise.try => + @_attachDebugger(win.webContents) + .then => if ua = options.userAgent @_setUserAgent(win.webContents, ua) @@ -105,15 +187,31 @@ module.exports = { ) .then -> win.loadURL(url) + .then => + ## enabling can only happen once the window has loaded + @_enableDebugger(win.webContents) .return(win) _attachDebugger: (webContents) -> + originalSendCommand = webContents.debugger.sendCommand + + webContents.debugger.sendCommand = (message, data = {}) -> + new Promise (resolve, reject) => + debug('debugger: sending %s %o', message, data) + + originalSendCommand.call webContents.debugger, message, data, (err, result) => + debug("debugger: received response for %s: %o", message, { err, result }) + if _.isEmpty(err) + return resolve(result) + reject(err) try webContents.debugger.attach() debug("debugger attached") catch err debug("debugger attached failed %o", { err }) + webContents.debugger.sendCommand('Browser.getVersion') + webContents.debugger.on "detach", (event, reason) -> debug("debugger detached due to %o", { reason }) @@ -121,7 +219,12 @@ module.exports = { if method is "Console.messageAdded" debug("console message: %o", params.message) - webContents.debugger.sendCommand("Console.enable") + _enableDebugger: (webContents) -> + debug("debugger: enable Console and Network") + Promise.join( + webContents.debugger.sendCommand("Console.enable"), + webContents.debugger.sendCommand("Network.enable") + ) _getPartition: (options) -> if options.isTextTerminal @@ -135,8 +238,8 @@ module.exports = { _clearCache: (webContents) -> debug("clearing cache") - new Promise (resolve) -> - webContents.session.clearCache(resolve) + Promise.fromCallback (cb) => + webContents.session.clearCache(cb) _setUserAgent: (webContents, userAgent) -> debug("setting user agent to:", userAgent) @@ -145,14 +248,14 @@ module.exports = { webContents.session.setUserAgent(userAgent) _setProxy: (webContents, proxyServer) -> - new Promise (resolve) -> + Promise.fromCallback (cb) => webContents.session.setProxy({ proxyRules: proxyServer - ## this should really only be necessary when + ## this should really only be necessary when ## running Chromium versions >= 72 ## https://github.com/cypress-io/cypress/issues/1872 proxyBypassRules: "<-loopback>" - }, resolve) + }, cb) open: (browser, url, options = {}, automation) -> { projectRoot, isTextTerminal } = options @@ -195,32 +298,7 @@ module.exports = { ## https://github.com/cypress-io/cypress/issues/1939 tryToCall(win, "focusOnWebView") - a = Windows.automation(win) - - invoke = (method, data) => - tryToCall win, -> - a[method](data) - - automation.use({ - onRequest: (message, data) -> - switch message - when "get:cookies" - invoke("getCookies", data) - when "get:cookie" - invoke("getCookie", data) - when "set:cookie" - invoke("setCookie", data) - when "clear:cookies" - invoke("clearCookies", data) - when "clear:cookie" - invoke("clearCookie", data) - when "is:automation:client:connected" - invoke("isAutomationConnected", data) - when "take:screenshot" - invoke("takeScreenshot") - else - throw new Error("No automation handler registered for: '#{message}'") - }) + automation.use(getAutomation(win)) events = new EE diff --git a/packages/server/lib/controllers/proxy.coffee b/packages/server/lib/controllers/proxy.coffee index e7e5e86c23e8..23a51e35c71c 100644 --- a/packages/server/lib/controllers/proxy.coffee +++ b/packages/server/lib/controllers/proxy.coffee @@ -45,7 +45,6 @@ isGzipError = (err) -> responseMustHaveEmptyBody = (method, statusCode) -> _.some([ _.includes(NO_BODY_STATUS_CODES, statusCode), - _.inRange(statusCode, 100, 200), _.invoke(method, 'toLowerCase') == 'head', ]) @@ -199,13 +198,17 @@ module.exports = { rewrite = (body) -> ## transparently decode their body to a node string and then re-encode nodeCharset = getNodeCharsetFromResponse(headers, body) - body = rewriter.html(iconv.decode(body, nodeCharset), remoteState.domainName, wantsInjection, wantsSecurityRemoved) - iconv.encode(body, nodeCharset) + decodedBody = iconv.decode(body, nodeCharset) + rewrittenBody = rewriter.html(decodedBody, remoteState.domainName, wantsInjection, wantsSecurityRemoved) + iconv.encode(rewrittenBody, nodeCharset) ## TODO: we can probably move this to the new ## replacestream rewriter instead of using ## a buffer injection = concat (body) -> + ## concat-stream yields an empty array if nothing is written + if _.isEqual(body, []) + body = Buffer.from('') ## if we're gzipped that means we need to unzip ## this content first, inject, and the rezip if isGzipped diff --git a/packages/server/lib/gui/auth.js b/packages/server/lib/gui/auth.js index 372541eef53b..99882e628b6d 100644 --- a/packages/server/lib/gui/auth.js +++ b/packages/server/lib/gui/auth.js @@ -177,7 +177,7 @@ const _stopServer = () => { authRedirectReached = false } -const _launchNativeAuth = (loginUrl, sendMessage) => { +const _launchNativeAuth = Promise.method((loginUrl, sendMessage) => { const warnCouldNotLaunch = () => { if (openExternalAttempted && !authRedirectReached) { sendMessage('warning', 'AUTH_COULD_NOT_LAUNCH_BROWSER', loginUrl) @@ -198,7 +198,7 @@ const _launchNativeAuth = (loginUrl, sendMessage) => { debug('Error launching native auth: %o', { err }) warnCouldNotLaunch() }) -} +}) module.exports = { _buildFullLoginUrl, diff --git a/packages/server/lib/gui/events.coffee b/packages/server/lib/gui/events.coffee index 104c9713da17..cbe563bdd2dc 100644 --- a/packages/server/lib/gui/events.coffee +++ b/packages/server/lib/gui/events.coffee @@ -91,13 +91,6 @@ handleEvent = (options, bus, event, id, type, arg) -> .then(send) .catch(sendErr) - when "clear:github:cookies" - Windows.getBrowserAutomation(event.sender) - .clearCookies({domain: "github.com"}) - .return(null) - .then(send) - .catch(sendErr) - when "external:open" shell.openExternal(arg) diff --git a/packages/server/lib/gui/menu.js b/packages/server/lib/gui/menu.js index af07d652a4d9..41b4964243b5 100644 --- a/packages/server/lib/gui/menu.js +++ b/packages/server/lib/gui/menu.js @@ -168,48 +168,7 @@ module.exports = { template.unshift({ label: name, - submenu: [ - { - label: `About ${name}`, - role: 'about', - }, - { - type: 'separator', - }, - { - label: 'Services', - role: 'services', - submenu: [], - }, - { - type: 'separator', - }, - { - label: `Hide ${name}`, - accelerator: 'Command+H', - role: 'hide', - }, - { - label: 'Hide Others', - accelerator: 'Command+Shift+H', - role: 'hideothers', - }, - { - label: 'Show All', - role: 'unhide', - }, - { - type: 'separator', - }, - { - label: 'Quit', - accelerator: 'Command+Q', - //role: "quit" ## must upgrade to latest electron - click: () => { - return process.exit(0) - }, - }, - ], + role: 'appMenu', }) } diff --git a/packages/server/lib/gui/windows.coffee b/packages/server/lib/gui/windows.coffee index cba8700cd0bf..2210888e5fce 100644 --- a/packages/server/lib/gui/windows.coffee +++ b/packages/server/lib/gui/windows.coffee @@ -68,68 +68,9 @@ module.exports = { getByWebContents: (webContents) -> BrowserWindow.fromWebContents(webContents) - getBrowserAutomation: (webContents) -> - win = @getByWebContents(webContents) - - @automation(win) - _newBrowserWindow: (options) -> new BrowserWindow(options) - automation: (win) -> - cookies = Promise.promisifyAll(win.webContents.session.cookies) - - return { - clear: (filter = {}) -> - clear = (cookie) => - url = getCookieUrl(cookie) - - cookies.removeAsync(url, cookie.name) - .return(cookie) - - @getAll(filter) - .map(clear) - - getAll: (filter) -> - cookies - .getAsync(filter) - - getCookies: (filter) -> - @getAll(filter) - - getCookie: (filter) -> - @getAll(filter) - .then(firstOrNull) - - setCookie: (props = {}) -> - ## only set the url if its not already present - props.url ?= getCookieUrl(props) - - ## resolve with the cookie props. the extension - ## calls back with the cookie details but electron - ## chrome API's do not. but it doesn't matter because - ## we always send a fully complete cookie props object - ## which can simply be returned. - cookies - .setAsync(props) - .return(props) - - clearCookie: (filter) -> - @clear(filter) - .then(firstOrNull) - - clearCookies: (filter) -> - @clear(filter) - - isAutomationConnected: -> - true - - takeScreenshot: -> - new Promise (resolve) -> - win.capturePage (img) -> - resolve(img.toDataURL()) - } - defaults: (options = {}) -> _.defaultsDeep(options, { x: null diff --git a/packages/server/lib/plugins/index.coffee b/packages/server/lib/plugins/index.coffee index 78e77b6a33c1..486d736cbe76 100644 --- a/packages/server/lib/plugins/index.coffee +++ b/packages/server/lib/plugins/index.coffee @@ -68,6 +68,7 @@ module.exports = { handleError = (err) -> debug("plugins process error:", err.stack) + return if not pluginsProcess ## prevent repeating this in case of multiple errors killPluginsProcess() err = errors.get("PLUGINS_ERROR", err.annotated or err.stack or err.message) err.title = "Error running plugin" diff --git a/packages/server/lib/request.coffee b/packages/server/lib/request.coffee index 1cbab199a46a..a0d0d1b58d11 100644 --- a/packages/server/lib/request.coffee +++ b/packages/server/lib/request.coffee @@ -4,23 +4,16 @@ rp = require("request-promise") url = require("url") tough = require("tough-cookie") debug = require("debug")("cypress:server:request") -moment = require("moment") Promise = require("bluebird") stream = require("stream") duplexify = require("duplexify") agent = require("@packages/network").agent statusCode = require("./util/status_code") streamBuffer = require("./util/stream_buffer").streamBuffer -Cookies = require("./automation/cookies") - -Cookie = tough.Cookie -CookieJar = tough.CookieJar - -## shallow clone the original -serializableProperties = Cookie.serializableProperties.slice(0) +SERIALIZABLE_COOKIE_PROPS = ['name', 'value', 'domain', 'expiry', 'path', 'secure', 'hostOnly', 'httpOnly'] NETWORK_ERRORS = "ECONNREFUSED ECONNRESET EPIPE EHOSTUNREACH EAI_AGAIN ENOTFOUND".split(" ") -VERBOSE_REQUEST_OPTS = "followRedirect jar strictSSL".split(" ") +VERBOSE_REQUEST_OPTS = "followRedirect strictSSL".split(" ") HTTP_CLIENT_REQUEST_EVENTS = "abort connect continue information socket timeout upgrade".split(" ") process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0" @@ -153,97 +146,6 @@ pick = (resp = {}) -> "Response Status": resp.statusCode } -setCookies = (cookies, jar, headers, url) => - return if _.isEmpty(cookies) - - if jar - cookies.forEach (c) -> - jar.setCookie(c, url, {ignoreError: true}) - - else - headers.Cookie = createCookieString(cookies) - -newCookieJar = -> - j = new CookieJar(undefined, {looseMode: true}) - - ## match the same api signature as @request - { - _jar: j - - toJSON: -> - ## temporarily include the URL property - ## and restore afterwards. this is used to fix - ## https://github.com/cypress-io/cypress/issues/1321 - Cookie.serializableProperties = serializableProperties.concat("url") - cookies = j.toJSON() - Cookie.serializableProperties = serializableProperties - return cookies - - setCookie: (cookieOrStr, uri, options) -> - ## store the original URL this cookie was set on - if cookie = j.setCookieSync(cookieOrStr, uri, options) - ## only set cookie URL if it was created correctly - ## since servers may send invalid cookies that fail - ## to parse - we may get undefined here - cookie.url = uri - - return cookie - - getCookieString: (uri) -> - j.getCookieStringSync(uri, {expire: false}) - - getCookies: (uri) -> - j.getCookiesSync(uri, {expire: false}) - } - -convertToJarCookie = (cookies = []) -> - _.map cookies, (cookie) -> - props = { - key: cookie.name - path: cookie.path - value: cookie.value - secure: cookie.secure - httpOnly: cookie.httpOnly - hostOnly: cookie.hostOnly - } - - ## hostOnly is the default when - ## NO DOMAIN= attribute was set - ## - ## so if we are not hostOnly then - ## this cookie WAS created with - ## a Domain= attribute and therefore - ## which lessens whichs domains this - ## cookie may be sent, and therefore - ## we need to set props.domain else - ## the domain would be implied by URL - if not cookie.hostOnly - ## https://github.com/salesforce/tough-cookie/issues/26 - ## we need to strip the leading dot - ## on domains else tough cookie will not - ## properly send these cookies. - ## we get dot leading domains from the - ## chrome cookie API's - props.domain = _.trimStart(cookie.domain, ".") - - ## if we have an expiry then this - ## is the number of seconds since the epoch - ## that this cookie expires. we need to convert - ## this to a JS date object - if cookie.expiry? - props.expires = moment.unix(cookie.expiry).toDate() - - return new Cookie(props) - -reduceCookieToArray = (c) -> - _.reduce c, (memo, val, key) -> - memo.push [key.trim(), val.trim()].join("=") - memo - , [] - -createCookieString = (c) -> - reduceCookieToArray(c).join("; ") - createRetryingRequestPromise = (opts) -> { requestId, @@ -459,10 +361,6 @@ module.exports = (options = {}) -> getDelayForRetry - reduceCookieToArray - - createCookieString - setDefaults create: (strOrOpts, promise) -> @@ -518,131 +416,95 @@ module.exports = (options = {}) -> return response - setJarCookies: (jar, automationFn) -> - setCookie = (cookie) -> + setRequestCookieHeader: (req, reqUrl, automationFn) -> + automationFn('get:cookies', { url: reqUrl }) + .then (cookies) -> + debug('getting cookies from browser %o', { reqUrl, cookies }) + header = cookies.map (cookie) -> + "#{cookie.name}=#{cookie.value}" + .join("; ") || undefined + req.headers.Cookie = header + header + + setCookiesOnBrowser: (res, resUrl, automationFn) -> + cookies = res.headers['set-cookie'] + if !cookies + return Promise.resolve() + + if !(cookies instanceof Array) + cookies = [cookies] + + parsedUrl = url.parse(resUrl) + debug('setting cookies on browser %o', { url: parsedUrl, cookies }) + + Promise.map cookies, (cookie) -> + cookie = tough.Cookie.parse(cookie, { loose: true }) cookie.name = cookie.key - ## TODO: fix this - return if cookie.name and cookie.name.startsWith("__cypress") - - ## tough-cookie will return us a cookie that looks like this.... - # { key: 'secret-session', - # value: 's%3AxMYoMAXnnMN2pzjYKJx21Id9zjQOaPsT.aKJv1mlfNlCEtrPUjgt48KX0c7xNiB%2Bb0fLijmi48dY', - # domain: 'session.foobar.com', - # path: '/', - # httpOnly: true, - # extensions: [ 'SameSite=Strict' ], - # hostOnly: true, - # creation: '2016-09-04T18:48:06.882Z', - # lastAccessed: '2016-09-04T18:48:06.882Z', - # name: 'secret-session' } - # - # { key: '2293-session', - # value: 'true', - # domain: 'localhost', - # path: '/', - # hostOnly: true, - # creation: '2016-09-05T03:03:20.780Z', - # lastAccessed: '2016-09-05T03:03:20.780Z', - # name: '2293-session' } - - switch - when cookie.maxAge? - ## when we have maxAge - ## prefer that - ## unix returns us time in seconds - ## from the epoc + we add that - ## to maxAge since thats relative seconds - ## from now - cookie.expiry = moment().unix() + cookie.maxAge - when ex = cookie.expires - ## tough cookie provides javascript date - ## formatted expires - cookie.expiry = moment(ex).unix() - - automationFn("set:cookie", cookie) - .then -> - ## the automation may return us null in - ## the case an expired cookie is removed - Cookies.normalizeCookieProps(cookie) - - Promise.try -> - store = jar.toJSON() - - debug("setting request jar cookies %o", store.cookies) - - ## this likely needs - ## to be an 'each' not a map - ## since we need to set cookies - ## in sequence and not all at once - ## because cookies could have colliding - ## values which need to be set in order - Promise.each(store.cookies, setCookie) + if not cookie.domain + ## take the domain from the URL + cookie.domain = parsedUrl.hostname + cookie.hostOnly = true + + return if not tough.domainMatch(cookie.domain, parsedUrl.hostname) + + expiry = cookie.expiryTime() + if isFinite(expiry) + cookie.expiry = expiry / 1000 + + cookie = _.pick(cookie, SERIALIZABLE_COOKIE_PROPS) + + if expiry <= 0 + return automationFn('clear:cookie', cookie) + automationFn('set:cookie', cookie) sendStream: (headers, automationFn, options = {}) -> _.defaults options, { headers: {} - jar: true onBeforeReqInit: (fn) -> fn() } if not caseInsensitiveGet(options.headers, "user-agent") and (ua = headers["user-agent"]) options.headers["user-agent"] = ua - ## create a new jar instance - ## unless its falsy or already set - if options.jar is true - options.jar = newCookieJar() - _.extend options, { strictSSL: false } self = @ - if jar = options.jar - followRedirect = options.followRedirect + followRedirect = options.followRedirect - options.followRedirect = (incomingRes) -> - ## if we have a cookie jar - req = @ + options.followRedirect = (incomingRes) -> + req = @ - newUrl = url.resolve(options.url, incomingRes.headers.location) + newUrl = url.resolve(options.url, incomingRes.headers.location) - ## and when we know we should follow the redirect - ## we need to override the init method and - ## first set the existing jar cookies on the browser - ## and then grab the cookies for the new url - req.init = _.wrap req.init, (orig, opts) => - options.onBeforeReqInit -> - self.setJarCookies(jar, automationFn) - .then -> - automationFn("get:cookies", {url: newUrl, includeHostOnly: true}) - .then(convertToJarCookie) - .then (cookies) -> - setCookies(cookies, jar, null, newUrl) - .then -> - orig.call(req, opts) + ## and when we know we should follow the redirect + ## we need to override the init method and + ## first set the received cookies on the browser + ## and then grab the cookies for the new url + req.init = _.wrap req.init, (orig, opts) => + options.onBeforeReqInit -> + self.setCookiesOnBrowser(incomingRes, options.url, automationFn) + .then (cookies) -> + self.setRequestCookieHeader(req, newUrl, automationFn) + .then (cookieHeader) -> + orig.call(req, opts) - followRedirect.call(req, incomingRes) + followRedirect.call(req, incomingRes) - automationFn("get:cookies", {url: options.url, includeHostOnly: true}) - .then(convertToJarCookie) - .then (cookies) -> - setCookies(cookies, options.jar, options.headers, options.url) + @setRequestCookieHeader(options, options.url, automationFn) .then => return => debug("sending request as stream %o", merge(options)) - str = @create(options) - str.getJar = -> options.jar - str + @create(options) sendPromise: (headers, automationFn, options = {}) -> _.defaults options, { headers: {} gzip: true - jar: true cookies: true followRedirect: true } @@ -661,11 +523,6 @@ module.exports = (options = {}) -> accept: "*/*" }) - ## create a new jar instance - ## unless its falsy or already set - if options.jar is true - options.jar = newCookieJar() - _.extend(options, { strictSSL: false simple: false @@ -683,10 +540,11 @@ module.exports = (options = {}) -> delete options.json delete options.body + self = @ + send = => ms = Date.now() - self = @ redirects = [] requestResponses = [] @@ -702,23 +560,18 @@ module.exports = (options = {}) -> push(incomingRes) - ## if we have a cookie jar - if jar = options.jar - req = @ - - ## and when we know we should follow the redirect - ## we need to override the init method and - ## first set the existing jar cookies on the browser - ## and then grab the cookies for the new url - req.init = _.wrap req.init, (orig, opts) => - self.setJarCookies(options.jar, automationFn) - .then -> - automationFn("get:cookies", {url: newUrl, includeHostOnly: true}) - .then(convertToJarCookie) - .then (cookies) -> - setCookies(cookies, jar, null, newUrl) - .then -> - orig.call(req, opts) + req = @ + + ## and when we know we should follow the redirect + ## we need to override the init method and + ## first set the new cookies on the browser + ## and then grab the cookies for the new url + req.init = _.wrap req.init, (orig, opts) => + self.setCookiesOnBrowser(incomingRes, options.url, automationFn) + .then -> + self.setRequestCookieHeader(req, newUrl, automationFn) + .then -> + orig.call(req, opts) ## cause the redirect to happen ## but swallow up the incomingRes @@ -743,29 +596,23 @@ module.exports = (options = {}) -> ## the current url resp.redirectedToUrl = url.resolve(options.url, loc) - if options.jar - @setJarCookies(options.jar, automationFn) - .return(resp) - else - resp + @setCookiesOnBrowser(resp, options.url, automationFn) + .return(resp) if c = options.cookies ## if we have a cookie object then just ## send the request up! if _.isObject(c) - setCookies(c, null, options.headers) + cookieHeader = _.keys(c).map (k) -> + "#{k}=#{c[k]}" + .join('; ') + if cookieHeader + options.headers.Cookie = cookieHeader send() else ## else go get the cookies first ## then make the request - - ## TODO: we can simply use the 'url' property on the cookies API - ## which automatically pulls all of the cookies that would be - ## set for that url! - automationFn("get:cookies", {url: options.url, includeHostOnly: true}) - .then(convertToJarCookie) - .then (cookies) -> - setCookies(cookies, options.jar, options.headers, options.url) + self.setRequestCookieHeader(options, options.url, automationFn) .then(send) else send() diff --git a/packages/server/lib/server.coffee b/packages/server/lib/server.coffee index a06491932dfe..fe6e7bb25d18 100644 --- a/packages/server/lib/server.coffee +++ b/packages/server/lib/server.coffee @@ -369,12 +369,11 @@ class Server if obj = buffers.getByOriginalUrl(urlStr) debug("got previous request buffer for url:", urlStr) - ## reset the cookies from the existing stream's jar + ## reset the cookies from the buffer on the browser return runPhase -> resolve( - request.setJarCookies(obj.jar, automationRequest) - .then (c) -> - return obj.details + Promise.map obj.details.cookies, _.partial(automationRequest, 'set:cookie') + .return(obj.details) ) redirects = [] @@ -412,15 +411,16 @@ class Server _.pick(incomingRes, "headers", "statusCode") ) - jar = str.getJar() + newUrl ?= urlStr runPhase => - request.setJarCookies(jar, automationRequest) - .then (c) => + ## get the cookies that would be sent with this request so they can be rehydrated + automationRequest("get:cookies", { + domain: cors.getSuperDomain(newUrl) + }) + .then (cookies) => @_remoteVisitingUrl = false - newUrl ?= urlStr - statusIs2xxOrAllowedFailure = -> ## is our status code in the 2xx range, or have we disabled failing ## on status code? @@ -434,7 +434,7 @@ class Server contentType url: newUrl status: incomingRes.statusCode - cookies: c + cookies statusText: statusCode.getText(incomingRes.statusCode) redirects originalUrl @@ -484,9 +484,8 @@ class Server buffers.set({ url: newUrl - jar: jar stream: responseBufferStream - details: details + details originalUrl: originalUrl response: incomingRes }) @@ -635,30 +634,7 @@ class Server {hostname} = url.parse("http://#{host}") onProxyErr = (err, req, res) -> - ## by default http-proxy will call socket.end - ## with no data, so we need to override the end - ## function and write our own response - ## https://github.com/nodejitsu/node-http-proxy/blob/master/lib/http-proxy/passes/ws-incoming.js#L159 - end = socket.end - socket.end = -> - socket.end = end - - response = [ - "HTTP/#{req.httpVersion} 502 #{statusCode.getText(502)}" - "X-Cypress-Proxy-Error-Message: #{err.message}" - "X-Cypress-Proxy-Error-Code: #{err.code}" - ].join("\r\n") + "\r\n\r\n" - - proxiedUrl = "#{protocol}//#{hostname}:#{port}" - - debug( - "Got ERROR proxying websocket connection to url: '%s' received error: '%s' with code '%s'", - proxiedUrl, - err.toString() - err.code - ) - - socket.end(response) + debug("Got ERROR proxying websocket connection", { err, port, protocol, hostname, req }) proxy.ws(req, socket, head, { secure: false diff --git a/packages/server/lib/util/chrome_policy_check.js b/packages/server/lib/util/chrome_policy_check.js index 8e550f7aec6d..a54978adabe6 100644 --- a/packages/server/lib/util/chrome_policy_check.js +++ b/packages/server/lib/util/chrome_policy_check.js @@ -99,7 +99,7 @@ module.exports = { */ if (os.platform() === 'win32') { try { - const registryJs = require('@cypress/registry-js') + const registryJs = require('registry-js') module.exports = { run: getRunner(registryJs), diff --git a/packages/server/lib/util/cors.js b/packages/server/lib/util/cors.js index e0608f766811..69e8f76d80ae 100644 --- a/packages/server/lib/util/cors.js +++ b/packages/server/lib/util/cors.js @@ -6,63 +6,83 @@ const parseDomain = require('parse-domain') const ipAddressRe = /^[\d\.]+$/ -module.exports = { - parseUrlIntoDomainTldPort (str) { - let { hostname, port, protocol } = url.parse(str) +function getSuperDomain (url) { + const parsed = parseUrlIntoDomainTldPort(url) - if (port == null) { - port = protocol === 'https:' ? '443' : '80' - } + return _.compact([parsed.domain, parsed.tld]).join('.') +} - let parsed = parseDomain(hostname, { - privateTlds: true, // use the public suffix - customTlds: ipAddressRe, - }) - - // if we couldn't get a parsed domain - if (!parsed) { - // then just fall back to a dumb check - // based on assumptions that the tld - // is the last segment after the final - // '.' and that the domain is the segment - // before that - const segments = hostname.split('.') - - parsed = { - tld: segments[segments.length - 1] || '', - domain: segments[segments.length - 2] || '', - } - } +function _parseDomain (domain, options = {}) { + return parseDomain(domain, _.defaults(options, { + privateTlds: true, + customTlds: ipAddressRe, + })) +} - const obj = {} +function parseUrlIntoDomainTldPort (str) { + let { hostname, port, protocol } = url.parse(str) - obj.port = port - obj.tld = parsed.tld - obj.domain = parsed.domain - // obj.protocol = protocol + if (port == null) { + port = protocol === 'https:' ? '443' : '80' + } - debug('Parsed URL %o', obj) + let parsed = _parseDomain(hostname) - return obj - }, + // if we couldn't get a parsed domain + if (!parsed) { + // then just fall back to a dumb check + // based on assumptions that the tld + // is the last segment after the final + // '.' and that the domain is the segment + // before that + const segments = hostname.split('.') - urlMatchesOriginPolicyProps (urlStr, props) { - // take a shortcut here in the case - // where remoteHostAndPort is null - if (!props) { - return false + parsed = { + tld: segments[segments.length - 1] || '', + domain: segments[segments.length - 2] || '', } + } + + const obj = {} + + obj.port = port + obj.tld = parsed.tld + obj.domain = parsed.domain + // obj.protocol = protocol + + debug('Parsed URL %o', obj) + + return obj +} + +function urlMatchesOriginPolicyProps (urlStr, props) { + // take a shortcut here in the case + // where remoteHostAndPort is null + if (!props) { + return false + } + + const parsedUrl = this.parseUrlIntoDomainTldPort(urlStr) + + // does the parsedUrl match the parsedHost? + return _.isEqual(parsedUrl, props) +} + +function urlMatchesOriginProtectionSpace (urlStr, origin) { + const normalizedUrl = uri.addDefaultPort(urlStr).format() + const normalizedOrigin = uri.addDefaultPort(origin).format() + + return _.startsWith(normalizedUrl, normalizedOrigin) +} + +module.exports = { + parseUrlIntoDomainTldPort, - const parsedUrl = this.parseUrlIntoDomainTldPort(urlStr) + parseDomain: _parseDomain, - // does the parsedUrl match the parsedHost? - return _.isEqual(parsedUrl, props) - }, + getSuperDomain, - urlMatchesOriginProtectionSpace (urlStr, origin) { - const normalizedUrl = uri.addDefaultPort(urlStr).format() - const normalizedOrigin = uri.addDefaultPort(origin).format() + urlMatchesOriginPolicyProps, - return _.startsWith(normalizedUrl, normalizedOrigin) - }, + urlMatchesOriginProtectionSpace, } diff --git a/packages/server/lib/util/electron_app.js b/packages/server/lib/util/electron_app.js index 0af8548b5db9..9180b559e28b 100644 --- a/packages/server/lib/util/electron_app.js +++ b/packages/server/lib/util/electron_app.js @@ -1,3 +1,5 @@ +const debug = require('debug')('cypress:server:electron_app') + const scale = () => { try { const { app } = require('electron') @@ -12,6 +14,13 @@ const ready = () => { const Promise = require('bluebird') const { app } = require('electron') + // electron >= 5.0.0 will exit the app if all browserwindows are closed, + // this is obviously undesirable in run mode + // https://github.com/cypress-io/cypress/pull/4720#issuecomment-514316695 + app.on('window-all-closed', () => { + debug('all BrowserWindows closed, not exiting') + }) + const waitForReady = () => { return new Promise((resolve) => { app.on('ready', resolve) diff --git a/packages/server/package.json b/packages/server/package.json index a4b162ca006c..f82f796eefd6 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -52,7 +52,7 @@ "chalk": "2.4.2", "charset": "1.0.1", "check-more-types": "2.24.0", - "chokidar": "3.0.1", + "chokidar": "3.0.2", "cjsxify": "0.3.0", "cli-table3": "0.5.1", "color-string": "1.5.3", @@ -102,7 +102,6 @@ "moment": "2.24.0", "morgan": "1.9.1", "node-machine-id": "1.1.10", - "node-offline-localhost": "0.9.0", "node-webkit-updater": "cypress-io/node-webkit-updater#e74623726f381487f543e373e71515177a32daeb", "opn": "cypress-io/opn#2f4e9a216ca7bdb95dfae9d46d99ddf004b3cbb5", "ospath": "1.2.2", @@ -153,14 +152,13 @@ "chokidar-cli": "1.2.2", "chrome-har-capturer": "0.13.4", "coffee-coverage": "3.0.1", - "console-table-printer": "1.0.0-beta12", "cors": "2.8.5", "eol": "0.9.1", "eventsource": "1.0.7", "express-session": "1.16.1", "express-useragent": "1.0.12", "http-mitm-proxy": "0.7.0", - "https-proxy-agent": "2.2.0", + "https-proxy-agent": "lpinca/node-https-proxy-agent#967f0115bddedbce0e4591c2a2250f988809dec8", "istanbul": "0.4.5", "mocked-env": "1.2.4", "mockery": "2.1.0", @@ -183,5 +181,8 @@ "config", "lib" ], - "productName": "Cypress" + "productName": "Cypress", + "optionalDependencies": { + "registry-js": "1.8.0" + } } diff --git a/packages/server/test/e2e/1_commands_outside_of_test_spec.coffee b/packages/server/test/e2e/1_commands_outside_of_test_spec.coffee index d5ad5c514d20..e7eb4c16f8a9 100644 --- a/packages/server/test/e2e/1_commands_outside_of_test_spec.coffee +++ b/packages/server/test/e2e/1_commands_outside_of_test_spec.coffee @@ -10,12 +10,6 @@ describe "e2e commands outside of test", -> ], (browser) -> it "[#{browser}] fails on cy commands", -> - ## TODO: remove this after electron upgrade - if browser is 'electron' - console.log('⚠️ skipping test in electron due to chromium 63 bug with sourceMaps') - ## this.skip() will not work here since it skips the afterEach - return - e2e.exec(@, { spec: "commands_outside_of_test_spec.coffee" snapshot: true @@ -24,12 +18,6 @@ describe "e2e commands outside of test", -> }) it "[#{browser}] fails on failing assertions", -> - ## TODO: remove this after electron upgrade - if browser is 'electron' - console.log('⚠️ skipping test in electron due to chromium 63 bug with sourceMaps') - ## this.skip() will not work here since it skips the afterEach - return - e2e.exec(@, { spec: "assertions_failing_outside_of_test_spec.coffee" snapshot: true diff --git a/packages/server/test/e2e/2_cookies_spec.coffee b/packages/server/test/e2e/2_cookies_spec.coffee index 6a5f8ff7b807..09f448c34923 100644 --- a/packages/server/test/e2e/2_cookies_spec.coffee +++ b/packages/server/test/e2e/2_cookies_spec.coffee @@ -80,9 +80,14 @@ describe "e2e cookies", -> } }) - it "passes", -> - e2e.exec(@, { - spec: "cookies_spec.coffee" - snapshot: true - expectedExitCode: 0 - }) + [ + "electron", + "chrome" + ].forEach (browser) -> + it "passes in #{browser}", -> + e2e.exec(@, { + spec: "cookies_spec.coffee" + snapshot: true + expectedExitCode: 0 + browser + }) diff --git a/packages/server/test/e2e/4_request_spec.coffee b/packages/server/test/e2e/4_request_spec.coffee index 3277f955fdaa..176a31b36eed 100644 --- a/packages/server/test/e2e/4_request_spec.coffee +++ b/packages/server/test/e2e/4_request_spec.coffee @@ -2,12 +2,7 @@ bodyParser = require("body-parser") cookieParser = require("cookie-parser") e2e = require("../support/helpers/e2e") -counts = { - "localhost:2290": 0 - "localhost:2291": 0 - "localhost:2292": 0 - "localhost:2293": 0 -} +counts = null urlencodedParser = bodyParser.urlencoded({ extended: false }) jsonParser = bodyParser.json() @@ -131,12 +126,25 @@ describe "e2e requests", -> }] }) - it "passes", -> - e2e.exec(@, { - spec: "request_spec.coffee" - snapshot: true - expectedExitCode: 0 - }) + beforeEach -> + counts = { + "localhost:2290": 0 + "localhost:2291": 0 + "localhost:2292": 0 + "localhost:2293": 0 + } + + [ + "electron", + "chrome" + ].forEach (browser) -> + it "passes in #{browser}", -> + e2e.exec(@, { + spec: "request_spec.coffee" + snapshot: true + expectedExitCode: 0 + browser + }) it "fails when network immediately fails", -> e2e.exec(@, { diff --git a/packages/server/test/e2e/5_subdomain_spec.coffee b/packages/server/test/e2e/5_subdomain_spec.coffee index a13ef487298f..e6504d266e90 100644 --- a/packages/server/test/e2e/5_subdomain_spec.coffee +++ b/packages/server/test/e2e/5_subdomain_spec.coffee @@ -102,14 +102,19 @@ describe "e2e subdomain", -> } }) - it "passes", -> - e2e.exec(@, { - spec: "subdomain_spec.coffee" - snapshot: true - expectedExitCode: 0 - config: { - hosts: { - "*.foobar.com": "127.0.0.1" + [ + "electron", + "chrome" + ].forEach (browser) -> + it "passes in #{browser}", -> + e2e.exec(@, { + spec: "subdomain_spec.coffee" + snapshot: true + expectedExitCode: 0 + config: { + hosts: { + "*.foobar.com": "127.0.0.1" + } } - } - }) + browser + }) diff --git a/packages/server/test/integration/cypress_spec.coffee b/packages/server/test/integration/cypress_spec.coffee index 2ebb03989a04..a9572d6a58df 100644 --- a/packages/server/test/integration/cypress_spec.coffee +++ b/packages/server/test/integration/cypress_spec.coffee @@ -123,7 +123,7 @@ describe "lib/cypress", -> .callThrough() .withArgs("INVOKED_BINARY_OUTSIDE_NPM_MODULE") .returns(null) - + sinon.spy(errors, "log") sinon.spy(errors, "logException") sinon.spy(console, "log") @@ -646,9 +646,8 @@ describe "lib/cypress", -> ## also make sure we test the rest of the integration functionality ## for headed errors! <-- not unit tests, but integration tests! it "logs error and exits when project folder has read permissions only and cannot write cypress.json", -> - if process.env.CI - ## Gleb: disabling this because Node 8 docker image runs as root - ## which makes accessing everything possible. + ## test disabled if running as root - root can write all things at all times + if process.geteuid() == 0 return permissionsPath = path.resolve("./permissions") @@ -775,7 +774,7 @@ describe "lib/cypress", -> debugger: { on: sinon.stub() attach: sinon.stub() - sendCommand: sinon.stub() + sendCommand: sinon.stub().callsArg(2) } setUserAgent: sinon.stub() session: { @@ -787,7 +786,6 @@ describe "lib/cypress", -> sinon.stub(browserUtils, "launch").resolves(ee) sinon.stub(Windows, "create").returns(ee) - sinon.stub(Windows, "automation") context "before:browser:launch", -> it "chrome", -> diff --git a/packages/server/test/integration/http_requests_spec.coffee b/packages/server/test/integration/http_requests_spec.coffee index b83d51b62aab..6a3c64ed1dbc 100644 --- a/packages/server/test/integration/http_requests_spec.coffee +++ b/packages/server/test/integration/http_requests_spec.coffee @@ -3123,7 +3123,7 @@ describe "Routes", -> afterEach -> @httpSrv.close() - [204, 304, 101, 102, 103].forEach (status) -> + [204, 304].forEach (status) -> it "passes through a #{status} response immediately", -> @rp({ url: "http://localhost:#{@port}/?status=#{status}" diff --git a/packages/server/test/integration/server_spec.coffee b/packages/server/test/integration/server_spec.coffee index 92f96ced8427..567d2c57892d 100644 --- a/packages/server/test/integration/server_spec.coffee +++ b/packages/server/test/integration/server_spec.coffee @@ -29,8 +29,8 @@ describe "Server", -> nock.enableNetConnect() @automationRequest = sinon.stub() - .withArgs("get:cookies").resolves([]) - .withArgs("set:cookie").resolves({}) + @automationRequest.withArgs("get:cookies").resolves([]) + @automationRequest.withArgs("set:cookie").resolves({}) @setup = (initialUrl, obj = {}) => if _.isObject(initialUrl) @@ -639,7 +639,6 @@ describe "Server", -> @server._onResolveUrl("http://localhost:64646", {}, @automationRequest) .catch (err) -> expect(err.message).to.eq("connect ECONNREFUSED 127.0.0.1:64646") - expect(err.stack).to.include("._errnoException") expect(err.port).to.eq(64646) expect(err.code).to.eq("ECONNREFUSED") diff --git a/packages/server/test/integration/websockets_spec.coffee b/packages/server/test/integration/websockets_spec.coffee index aceac43d7a84..27fbfbf3ab87 100644 --- a/packages/server/test/integration/websockets_spec.coffee +++ b/packages/server/test/integration/websockets_spec.coffee @@ -14,7 +14,7 @@ Automation = require("#{root}lib/automation") Fixtures = require("#{root}/test/support/helpers/fixtures") cyPort = 12345 -otherPort = 5555 +otherPort = 55551 wsPort = 20000 wssPort = 8443 @@ -59,7 +59,7 @@ describe "Web Sockets", -> expect(err.code).to.eq("ECONNRESET") done() - it "sends back 502 Bad Gateway when error upgrading", (done) -> + it "sends back ECONNRESET when error upgrading", (done) -> agent = new httpsAgent("http://localhost:#{cyPort}") @server._onDomainSet("http://localhost:#{otherPort}") @@ -68,11 +68,9 @@ describe "Web Sockets", -> agent: agent }) - client.on "unexpected-response", (req, res) -> - expect(res.statusCode).to.eq(502) - expect(res.statusMessage).to.eq("Bad Gateway") - expect(res.headers).to.have.property("x-cypress-proxy-error-message") - expect(res.headers).to.have.property("x-cypress-proxy-error-code") + client.on "error", (err) -> + expect(err.code).to.eq('ECONNRESET') + expect(err.message).to.eq('socket hang up') done() diff --git a/packages/server/test/performance/proxy_performance_spec.js b/packages/server/test/performance/proxy_performance_spec.js index 3452d6e218f5..4e33d1219d8f 100644 --- a/packages/server/test/performance/proxy_performance_spec.js +++ b/packages/server/test/performance/proxy_performance_spec.js @@ -12,7 +12,6 @@ const DebuggingProxy = require('@cypress/debugging-proxy') const HarCapturer = require('chrome-har-capturer') const performance = require('../support/helpers/performance') const Promise = require('bluebird') -const Table = require('console-table-printer').Table const sanitizeFilename = require('sanitize-filename') process.env.CYPRESS_ENV = 'development' @@ -387,14 +386,10 @@ describe('Proxy Performance', function () { after(() => { debug(`Done in ${Math.round((new Date() / 1000) - start)}s`) - // console.table not available until Node 10 - const t = new Table() - - t.addRows(testCases) - - // console.log is bad for eslint, but nobody never said nothing about process.stdout.write process.stdout.write('Note: All times are in milliseconds.\n') - t.printTable() + + // eslint-disable-next-line no-console + console.table(testCases) return Promise.map(testCases, (testCase) => { testCase['URL'] = urlUnderTest diff --git a/packages/server/test/scripts/e2e.js b/packages/server/test/scripts/e2e.js index 1276e7ab5594..20e7737760b7 100644 --- a/packages/server/test/scripts/e2e.js +++ b/packages/server/test/scripts/e2e.js @@ -37,7 +37,9 @@ glob('test/e2e/**/*') .then((specs = []) => { if (options.spec) { return _.filter(specs, (spec) => { - return spec.includes(options.spec) + return _.some(options.spec.split(','), (specPart) => { + return spec.includes(specPart) + }) }) } diff --git a/packages/server/test/scripts/run.js b/packages/server/test/scripts/run.js index c804473db0bd..4fd9cd41e2e6 100644 --- a/packages/server/test/scripts/run.js +++ b/packages/server/test/scripts/run.js @@ -24,6 +24,10 @@ const isWindows = () => { return os.platform() === 'win32' } +const isGteNode12 = () => { + return Number(process.versions.node.split('.')[0]) >= 12 +} + if (!run) { return exitErr(` Error: A path to a spec file must be specified! @@ -64,6 +68,14 @@ if (options['inspect-brk']) { ) } +if (isGteNode12()) { + // max HTTP header size 8kb -> 1mb + // https://github.com/cypress-io/cypress/issues/76 + commandAndArguments.args.push( + `--max-http-header-size=${1024 * 1024}` + ) +} + commandAndArguments.args.push( 'node_modules/.bin/_mocha', run diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/subdomain_spec.coffee b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/subdomain_spec.coffee index 10abee2177a5..08d712e80cd3 100644 --- a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/subdomain_spec.coffee +++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/subdomain_spec.coffee @@ -38,7 +38,7 @@ describe "subdomains", -> occurences = Cypress._.compact(cookie.split("secret-session")) expect(occurences).to.have.length(1) - it "corrects sets domain based cookies", -> + it "correctly sets domain based cookies", -> cy .visit("http://domain.foobar.com:2292") .getCookies().should("have.length", 1) diff --git a/packages/server/test/support/helpers/e2e.coffee b/packages/server/test/support/helpers/e2e.coffee index a380fd972814..ecf3973e266c 100644 --- a/packages/server/test/support/helpers/e2e.coffee +++ b/packages/server/test/support/helpers/e2e.coffee @@ -91,6 +91,7 @@ normalizeStdout = (str, options = {}) -> .replace(/\((\d+ minutes?,\s+)?\d+ seconds?\)/g, "(X seconds)") .replace(/\r/g, "") .replace(/(Uploading Results.*?\n\n)((.*-.*[\s\S\r]){2,}?)(\n\n)/g, replaceUploadingResults) ## replaces multiple lines of uploading results (since order not guaranteed) + .replace(/^(\- )(\/.*\/packages\/server\/)(.*)$/gm, "$1$3") ## fix "Require stacks" for CI if options.browser isnt undefined and options.browser isnt 'electron' str = str.replace(/\(\d{2,4}x\d{2,4}\)/g, "(YYYYxZZZZ)") ## screenshot dimensions diff --git a/packages/server/test/unit/browsers/electron_spec.coffee b/packages/server/test/unit/browsers/electron_spec.coffee index 5fcd43cffd4b..9b1b0492239e 100644 --- a/packages/server/test/unit/browsers/electron_spec.coffee +++ b/packages/server/test/unit/browsers/electron_spec.coffee @@ -34,6 +34,11 @@ describe "lib/browsers/electron", -> remove: sinon.stub() } } + "debugger": { + attach: sinon.stub().returns() + sendCommand: sinon.stub().resolves() + on: sinon.stub().returns() + } } }) @@ -343,7 +348,7 @@ describe "lib/browsers/electron", -> it "sets proxy rules for webContents", -> webContents = { session: { - setProxy: sinon.stub().yieldsAsync() + setProxy: sinon.stub().callsArg(1) } } @@ -353,3 +358,108 @@ describe "lib/browsers/electron", -> proxyRules: "proxy rules" proxyBypassRules: "<-loopback>" }) + + context "._getAutomation", -> + beforeEach -> + @sendCommand = @win.webContents.debugger.sendCommand + + @sendCommand.throws() + .withArgs('Browser.getVersion').resolves() + + @onRequest = electron._getAutomation(@win).onRequest + + describe "get:cookies", -> + beforeEach -> + @sendCommand.withArgs('Network.getAllCookies') + .resolves({ + cookies: [ + {name: "foo", value: "f", path: "/", domain: "localhost", secure: true, httpOnly: true, expires: 123} + {name: "bar", value: "b", path: "/", domain: "localhost", secure: false, httpOnly: false, expires: 456} + ] + }) + + it "returns all cookies", -> + @onRequest('get:cookies', { domain: "localhost" }) + .then (resp) -> + expect(resp).to.deep.eq([ + {name: "foo", value: "f", path: "/", domain: "localhost", secure: true, httpOnly: true, expirationDate: 123} + {name: "bar", value: "b", path: "/", domain: "localhost", secure: false, httpOnly: false, expirationDate: 456} + ]) + + describe "get:cookie", -> + beforeEach -> + @sendCommand.withArgs('Network.getAllCookies') + .resolves({ + cookies: [ + {name: "session", value: "key", path: "/login", domain: "google.com", secure: true, httpOnly: true, expires: 123} + ] + }) + + it "returns a specific cookie by name", -> + @onRequest('get:cookie', {domain: "google.com", name: "session"}) + .then (resp) -> + expect(resp).to.deep.eq({name: "session", value: "key", path: "/login", domain: "google.com", secure: true, httpOnly: true, expirationDate: 123}) + + it "returns null when no cookie by name is found", -> + @onRequest('get:cookie', {domain: "google.com", name: "doesNotExist"}) + .then (resp) -> + expect(resp).to.be.null + + describe "set:cookie", -> + beforeEach -> + @sendCommand.withArgs('Network.setCookie', {domain: ".google.com", name: "session", value: "key", path: "/", expires: undefined}) + .resolves({ success: true }) + .withArgs('Network.setCookie', {domain: "foo", path: "/bar", name: "", value: "", expires: undefined}) + .rejects(new Error("some error")) + .withArgs('Network.getAllCookies') + .resolves({ + cookies: [ + {name: "session", value: "key", path: "/", domain: ".google.com", secure: false, httpOnly: false} + ] + }) + + it "resolves with the cookie props", -> + @onRequest('set:cookie', {domain: "google.com", name: "session", value: "key", path: "/"}) + .then (resp) -> + expect(resp).to.deep.eq({domain: ".google.com", expirationDate: undefined, httpOnly: false, name: "session", value: "key", path: "/", secure: false}) + + it "rejects with error", -> + @onRequest('set:cookie', {domain: "foo", path: "/bar"}) + .then -> + throw new Error("should have failed") + .catch (err) -> + expect(err.message).to.eq("some error") + + describe "clear:cookie", -> + beforeEach -> + @sendCommand.withArgs('Network.getAllCookies') + .resolves({ + cookies: [ + {name: "session", value: "key", path: "/", domain: "google.com", secure: true, httpOnly: true, expires: 123} + {name: "shouldThrow", value: "key", path: "/assets", domain: "cdn.github.com", secure: false, httpOnly: true, expires: 123} + ] + }) + + @sendCommand.withArgs('Network.deleteCookies', { domain: "cdn.github.com", name: "shouldThrow" }) + .rejects(new Error("some error")) + .withArgs('Network.deleteCookies') + .resolves() + + it "resolves single removed cookie", -> + @onRequest('clear:cookie', {domain: "google.com", name: "session"}) + .then (resp) -> + expect(resp).to.deep.eq( + {name: "session", value: "key", path: "/", domain: "google.com", secure: true, httpOnly: true, expirationDate: 123} + ) + + it "returns null when no cookie by name is found", -> + @onRequest('clear:cookie', {domain: "google.com", name: "doesNotExist"}) + .then (resp) -> + expect(resp).to.be.null + + it "rejects with error", -> + @onRequest('clear:cookie', {domain: "cdn.github.com", name: "shouldThrow"}) + .then -> + throw new Error("should have failed") + .catch (err) -> + expect(err.message).to.eq("some error") diff --git a/packages/server/test/unit/gui/events_spec.coffee b/packages/server/test/unit/gui/events_spec.coffee index 92b54ef0f412..4311e92c94f6 100644 --- a/packages/server/test/unit/gui/events_spec.coffee +++ b/packages/server/test/unit/gui/events_spec.coffee @@ -134,30 +134,6 @@ describe "lib/gui/events", -> @handleEvent("get:current:user").then (assert) => assert.sendErrCalledWith(err) - context "cookies", -> - describe "clear:github:cookies", -> - it "clears cookies and returns null", -> - sinon.stub(Windows, "getBrowserAutomation") - .withArgs(@event.sender) - .returns({ - clearCookies: sinon.stub().withArgs({domain: "github.com"}).resolves() - }) - - @handleEvent("clear:github:cookies").then (assert) => - assert.sendCalledWith(null) - - it "catches errors", -> - err = new Error("foo") - - sinon.stub(Windows, "getBrowserAutomation") - .withArgs(@event.sender) - .returns({ - clearCookies: sinon.stub().withArgs({domain: "github.com"}).rejects(err) - }) - - @handleEvent("clear:github:cookies", {foo: "bar"}).then (assert) => - assert.sendErrCalledWith(err) - context "external shell", -> describe "external:open", -> it "shell.openExternal with arg", -> diff --git a/packages/server/test/unit/gui/menu_spec.js b/packages/server/test/unit/gui/menu_spec.js index aedd5e1ec0cf..96b8c0cef033 100644 --- a/packages/server/test/unit/gui/menu_spec.js +++ b/packages/server/test/unit/gui/menu_spec.js @@ -36,49 +36,17 @@ describe('gui/menu', function () { }) context('Cypress', function () { - describe('on macOS', function () { - it('contains about, services, hide, hide others, show all, quit', () => { - menu.set() - const labels = getLabels(getMenuItem('Cypress').submenu) - - expect(labels).to.eql([ - 'About Cypress', - 'Services', - 'Hide Cypress', - 'Hide Others', - 'Show All', - 'Quit', - ]) - }) - - it('sets roles and shortcuts', () => { - menu.set() - const cyMenu = getMenuItem('Cypress') - - expect(getSubMenuItem(cyMenu, 'About Cypress').role).to.equal('about') - expect(getSubMenuItem(cyMenu, 'Services').role).to.equal('services') - expect(getSubMenuItem(cyMenu, 'Hide Cypress').role).to.equal('hide') - expect(getSubMenuItem(cyMenu, 'Hide Cypress').accelerator).to.equal('Command+H') - expect(getSubMenuItem(cyMenu, 'Hide Others').role).to.equal('hideothers') - expect(getSubMenuItem(cyMenu, 'Hide Others').accelerator).to.equal('Command+Shift+H') - expect(getSubMenuItem(cyMenu, 'Show All').role).to.equal('unhide') - expect(getSubMenuItem(cyMenu, 'Quit').accelerator).to.equal('Command+Q') - }) + it('on darwin has appMenu role', () => { + menu.set() + const cyMenu = getMenuItem('Cypress') - it('exits process when Quit is clicked', () => { - sinon.stub(process, 'exit') - menu.set() - getSubMenuItem(getMenuItem('Cypress'), 'Quit').click() - expect(process.exit).to.be.calledWith(0) - }) + expect(cyMenu.role).to.eq('appMenu') }) - describe('other OS', () => { - it('does not exist', () => { - os.platform.returns('linux') - menu.set() - expect(getMenuItem('Cypress')).to.be.undefined - }) + it('on other OS does not exist', () => { + os.platform.returns('linux') + menu.set() + expect(getMenuItem('Cypress')).to.be.undefined }) }) diff --git a/packages/server/test/unit/gui/windows_spec.coffee b/packages/server/test/unit/gui/windows_spec.coffee index 99edba77309d..c54562d5bb9d 100644 --- a/packages/server/test/unit/gui/windows_spec.coffee +++ b/packages/server/test/unit/gui/windows_spec.coffee @@ -32,17 +32,6 @@ describe "lib/gui/windows", -> afterEach -> Windows.reset() - context ".getBrowserAutomation", -> - beforeEach -> - sinon.stub(Windows, "automation") - sinon.stub(Windows, "getByWebContents") - - it "gets window and passes to electron.automation", -> - Windows.getByWebContents.withArgs("foo").returns("bar") - Windows.automation.withArgs("bar").returns("baz") - - expect(Windows.getBrowserAutomation("foo")).to.eq("baz") - context ".getByWebContents", -> beforeEach -> sinon.stub(BrowserWindow, "fromWebContents") @@ -174,164 +163,3 @@ describe "lib/gui/windows", -> .delay(100) .then () => expect(@state.set).to.be.calledWith({whatsUpWithDevTools: false}) - - context ".automation", -> - beforeEach -> - @cookies = { - set: sinon.stub() - get: sinon.stub() - remove: sinon.stub() - } - - @win = { - webContents: { - session: { - cookies: @cookies - } - } - } - - @automation = Windows.automation(@win) - - describe ".getCookies", -> - beforeEach -> - @cookies.get - .withArgs({domain: "localhost"}) - .yieldsAsync(null, [ - {name: "foo", value: "f", path: "/", domain: "localhost", secure: true, httpOnly: true, expiry: 123} - {name: "bar", value: "b", path: "/", domain: "localhost", secure: false, httpOnly: false, expiry: 456} - ]) - - it "returns all cookies", -> - @automation.getCookies({domain: "localhost"}) - .then (resp) -> - expect(resp).to.deep.eq([ - {name: "foo", value: "f", path: "/", domain: "localhost", secure: true, httpOnly: true, expiry: 123} - {name: "bar", value: "b", path: "/", domain: "localhost", secure: false, httpOnly: false, expiry: 456} - ]) - - describe ".getCookie", -> - beforeEach -> - @cookies.get - .withArgs({domain: "google.com", name: "session"}) - .yieldsAsync(null, [ - {name: "session", value: "key", path: "/login", domain: "google", secure: true, httpOnly: true, expiry: 123} - ]) - .withArgs({domain: "google.com", name: "doesNotExist"}) - .yieldsAsync(null, []) - - it "returns a specific cookie by name", -> - @automation.getCookie({domain: "google.com", name: "session"}) - .then (resp) -> - expect(resp).to.deep.eq({name: "session", value: "key", path: "/login", domain: "google", secure: true, httpOnly: true, expiry: 123}) - - it "returns null when no cookie by name is found", -> - @automation.getCookie({domain: "google.com", name: "doesNotExist"}) - .then (resp) -> - expect(resp).to.be.null - - describe ".setCookie", -> - beforeEach -> - @cookies.set - .withArgs({domain: "google.com", name: "session", value: "key", path: "/", url: "http://google.com/"}) - .yieldsAsync(null, - {name: "session", value: "key", path: "/", domain: "google", secure: false, httpOnly: false} - ) - .withArgs({domain: "foo", path: "/bar", url: "http://foo/bar"}) - .yieldsAsync(new Error("some error")) - - it "resolves with the cookie props", -> - @automation.setCookie({domain: "google.com", name: "session", value: "key", path: "/"}) - .then (resp) -> - expect(resp).to.deep.eq({domain: "google.com", name: "session", value: "key", path: "/", url: "http://google.com/"}) - - it "rejects with error", -> - @automation.setCookie({domain: "foo", path: "/bar", url: "http://foo/bar"}) - .then -> - throw new Error("should have failed") - .catch (err) -> - expect(err.message).to.eq("some error") - - describe ".clearCookies", -> - beforeEach -> - @cookies.get - .withArgs({domain: "google.com"}) - .yieldsAsync(null, [ - {name: "session", value: "key", path: "/", domain: "google.com", secure: true, httpOnly: true, expiry: 123} - {name: "foo", value: "bar", path: "/foo", domain: "google.com", secure: false, httpOnly: false, expiry: 456} - ]) - .withArgs({domain: "cdn.github.com"}) - .yieldsAsync(null, [ - {name: "shouldThrow", value: "key", path: "/assets", domain: "cdn.github.com", secure: false, httpOnly: true, expiry: 123} - ]) - - @cookies.remove - .withArgs("https://google.com/", "session") - .yieldsAsync(null) - - .withArgs("http://google.com/foo", "foo") - .yieldsAsync(null) - - .withArgs("http://cdn.github.com/assets", "shouldThrow") - .yieldsAsync(new Error("some error")) - - it "resolves with array of removed cookies", -> - @automation.clearCookies({domain: "google.com"}) - .then (resp) -> - expect(resp).to.deep.eq([ - {name: "session", value: "key", path: "/", domain: "google.com", secure: true, httpOnly: true, expiry: 123} - {name: "foo", value: "bar", path: "/foo", domain: "google.com", secure: false, httpOnly: false, expiry: 456} - ]) - - it "rejects with error", -> - @automation.clearCookies({domain: "cdn.github.com"}) - .then -> - throw new Error("should have failed") - .catch (err) -> - expect(err.message).to.eq("some error") - - describe ".clearCookie", -> - beforeEach -> - @cookies.get - .withArgs({domain: "google.com", name: "session"}) - .yieldsAsync(null, [ - {name: "session", value: "key", path: "/", domain: "google.com", secure: true, httpOnly: true, expiry: 123} - ]) - - .withArgs({domain: "google.com", name: "doesNotExist"}) - .yieldsAsync(null, []) - - .withArgs({domain: "cdn.github.com", name: "shouldThrow"}) - .yieldsAsync(null, [ - {name: "shouldThrow", value: "key", path: "/assets", domain: "cdn.github.com", secure: false, httpOnly: true, expiry: 123} - ]) - - @cookies.remove - .withArgs("https://google.com/", "session") - .yieldsAsync(null) - - .withArgs("http://cdn.github.com/assets", "shouldThrow") - .yieldsAsync(new Error("some error")) - - it "resolves single removed cookie", -> - @automation.clearCookie({domain: "google.com", name: "session"}) - .then (resp) -> - expect(resp).to.deep.eq( - {name: "session", value: "key", path: "/", domain: "google.com", secure: true, httpOnly: true, expiry: 123} - ) - - it "returns null when no cookie by name is found", -> - @automation.clearCookie({domain: "google.com", name: "doesNotExist"}) - .then (resp) -> - expect(resp).to.be.null - - it "rejects with error", -> - @automation.clearCookie({domain: "cdn.github.com", name: "shouldThrow"}) - .then -> - throw new Error("should have failed") - .catch (err) -> - expect(err.message).to.eq("some error") - - describe "isAutomationConnected", -> - it "returns true", -> - expect(@automation.isAutomationConnected()).to.be.true diff --git a/packages/server/test/unit/plugins/child/run_plugins_spec.coffee b/packages/server/test/unit/plugins/child/run_plugins_spec.coffee index 13500b50c3f3..a2ffdea0bed6 100644 --- a/packages/server/test/unit/plugins/child/run_plugins_spec.coffee +++ b/packages/server/test/unit/plugins/child/run_plugins_spec.coffee @@ -12,12 +12,10 @@ Fixtures = require("#{root}../../test/support/helpers/fixtures") colorCodeRe = /\[[0-9;]+m/gm pathRe = /\/?([a-z0-9_-]+\/)*[a-z0-9_-]+\/([a-z_]+\.\w+)[:0-9]+/gmi -stackPathRe = /\(?\/?([a-z0-9_-]+\/)*([a-z0-9_-]+\.\w+)[:0-9]+\)?/gmi withoutStack = (err) -> _.omit(err, "stack") withoutColorCodes = (str) -> str.replace(colorCodeRe, "") withoutPath = (str) -> str.replace(pathRe, '$2)') -withoutStackPaths = (stack) -> stack.replace(stackPathRe, '$2') describe "lib/plugins/child/run_plugins", -> beforeEach -> @@ -35,7 +33,7 @@ describe "lib/plugins/child/run_plugins", -> mockery.registerSubstitute("plugins-file", "/does/not/exist.coffee") runPlugins(@ipc, "plugins-file") expect(@ipc.send).to.be.calledWith("load:error", "PLUGINS_FILE_ERROR", "plugins-file") - snapshot(withoutStackPaths(@ipc.send.lastCall.args[3])) + snapshot(@ipc.send.lastCall.args[3].split('\n')[0]) it "sends error message if requiring pluginsFile errors", -> ## path for substitute is relative to lib/plugins/child/plugins_child.js @@ -45,7 +43,7 @@ describe "lib/plugins/child/run_plugins", -> ) runPlugins(@ipc, "plugins-file") expect(@ipc.send).to.be.calledWith("load:error", "PLUGINS_FILE_ERROR", "plugins-file") - snapshot(withoutStackPaths(@ipc.send.lastCall.args[3])) + snapshot(@ipc.send.lastCall.args[3].split('\n')[0]) it "sends error message if pluginsFile has syntax error", -> ## path for substitute is relative to lib/plugins/child/plugins_child.js diff --git a/packages/server/test/unit/request_spec.coffee b/packages/server/test/unit/request_spec.coffee index e9b85490d0ea..f98680fb357e 100644 --- a/packages/server/test/unit/request_spec.coffee +++ b/packages/server/test/unit/request_spec.coffee @@ -7,18 +7,14 @@ Request = require("#{root}lib/request") request = Request({timeout: 100}) describe "lib/request", -> + beforeEach -> + @fn = sinon.stub() + @fn.withArgs('set:cookie').resolves({}) + @fn.withArgs('get:cookies').resolves([]) + it "is defined", -> expect(request).to.be.an("object") - context "#reduceCookieToArray", -> - it "converts object to array of key values", -> - obj = { - foo: "bar" - baz: "quux" - } - - expect(request.reduceCookieToArray(obj)).to.deep.eq(["foo=bar", "baz=quux"]) - context "#getDelayForRetry", -> it "divides by 10 when delay >= 1000 and err.code = ECONNREFUSED", -> retryIntervals = [1,2,3,4] @@ -104,15 +100,6 @@ describe "lib/request", -> expect(opts.delaysRemaining).to.eq(delaysRemaining) - context "#createCookieString", -> - it "joins array by '; '", -> - obj = { - foo: "bar" - baz: "quux" - } - - expect(request.createCookieString(obj)).to.eq("foo=bar; baz=quux") - context "#normalizeResponse", -> beforeEach -> @push = sinon.stub() @@ -264,9 +251,6 @@ describe "lib/request", -> expect(@hits).to.eq(5) context "#sendPromise", -> - beforeEach -> - @fn = sinon.stub() - it "sets strictSSL=false", -> init = sinon.spy(request.rp.Request.prototype, "init") @@ -335,6 +319,8 @@ describe "lib/request", -> ]) it "includes redirects", -> + @fn.resolves() + nock("http://www.github.com") .get("/dashboard") .reply(301, null, { @@ -623,6 +609,9 @@ describe "lib/request", -> expect(resp.status).to.eq(200) context "followRedirect", -> + beforeEach -> + @fn.resolves() + it "by default follow redirects", -> nock("http://localhost:8080") .get("/dashboard") @@ -817,7 +806,7 @@ describe "lib/request", -> .then -> throw new Error("should have failed") .catch (err) -> - expect(err.message).to.eq "TypeError: The header content contains invalid characters" + expect(err.message).to.eq "TypeError [ERR_INVALID_CHAR]: Invalid character in header content [\"x-text\"]" it "handles weird content in the body just fine", -> request.sendPromise({}, @fn, { @@ -830,9 +819,6 @@ describe "lib/request", -> }) context "#sendStream", -> - beforeEach -> - @fn = sinon.stub() - it "allows overriding user-agent in headers", -> nock("http://localhost:8080") .matchHeader("user-agent", "custom-agent") diff --git a/packages/server/test/unit/socket_spec.coffee b/packages/server/test/unit/socket_spec.coffee index b1b9e48bb4cf..df551597101c 100644 --- a/packages/server/test/unit/socket_spec.coffee +++ b/packages/server/test/unit/socket_spec.coffee @@ -143,7 +143,7 @@ describe "lib/socket", -> it "does not clear any namespaced cookies", (done) -> sinon.stub(chrome.cookies, "getAll") - .withArgs({name: "session"}) + .withArgs({name: "session", domain: "google.com"}) .yieldsAsync([ {name: "session", value: "key", path: "/", domain: "google.com", secure: true, httpOnly: true, expirationDate: 123, a: "a", b: "c"} ]) diff --git a/packages/static/gulpfile.js b/packages/static/gulpfile.js index a356a30e966a..e6282003ac01 100644 --- a/packages/static/gulpfile.js +++ b/packages/static/gulpfile.js @@ -1,9 +1,7 @@ const gulp = require('gulp') const icons = require('@cypress/icons') -gulp.task('favicon', () => { +exports.build = () => { return gulp.src(icons.getPathToFavicon('**/**')) .pipe(gulp.dest('./dist')) -}) - -gulp.task('build', gulp.parallel('favicon')) +} diff --git a/packages/web-config/package.json b/packages/web-config/package.json index cc75188add01..9eba10c940ff 100644 --- a/packages/web-config/package.json +++ b/packages/web-config/package.json @@ -28,7 +28,7 @@ "jsdom": "13.2.0", "mini-css-extract-plugin": "0.5.0", "mock-require": "3.0.3", - "node-sass": "4.11.0", + "node-sass": "4.12.0", "node-sass-globbing": "0.0.23", "postcss-loader": "3.0.0", "rebuild-node-sass": "1.1.0", diff --git a/scripts/binary/bump.coffee b/scripts/binary/bump.coffee index d4fb94f40cfd..53ebcc972c88 100644 --- a/scripts/binary/bump.coffee +++ b/scripts/binary/bump.coffee @@ -1,4 +1,3 @@ -require("console.table") _ = require("lodash") fs = require("fs-extra") Promise = require("bluebird") @@ -119,7 +118,8 @@ awaitEachProjectAndProvider = (projects, fn, filter = R.identity) -> filteredProjects = R.filter(filter, projects) if check.empty(filteredProjects) console.log("⚠️ zero filtered projects left after filtering") - console.table("filtered projects", filteredProjects) + console.log("filtered projects:") + console.table(filteredProjects) Promise.mapSeries filteredProjects, (project) -> fn(project.repo, project.provider, creds) @@ -152,7 +152,8 @@ module.exports = { nextVersion: (version) -> MAIN_PROJECTS = remapMain(_PROVIDERS) console.log("Setting next version to build", version) - console.table("In these projects", MAIN_PROJECTS) + console.log("In these projects:") + console.table(MAIN_PROJECTS) la(check.unemptyString(version), "missing next version to set", version) @@ -168,7 +169,8 @@ module.exports = { # in each project, set a couple of environment variables version: (nameOrUrl, binaryVersionOrUrl, platform, providerName) -> - console.table("All possible projects", PROJECTS) + console.log("All possible projects:") + console.table(PROJECTS) la(check.unemptyString(nameOrUrl), "missing cypress name or url to set", nameOrUrl) diff --git a/scripts/run-docker-local.sh b/scripts/run-docker-local.sh index b94204839d2c..71e5e7ff3aa3 100755 --- a/scripts/run-docker-local.sh +++ b/scripts/run-docker-local.sh @@ -2,7 +2,7 @@ set e+x echo "This script should be run from cypress's root" -name=cypress/browsers:node8.9.3-chrome73 +name=cypress/browsers:node12.0.0-chrome73 echo "Pulling CI container $name" docker pull $name