Merge branch 'master' into docs/cypress-basics

This commit is contained in:
Loren Norman
2017-05-25 14:44:53 -04:00
106 changed files with 1259 additions and 760 deletions
+57 -60
View File
@@ -3,9 +3,13 @@ version: 2
jobs:
build:
docker:
# the Docker image with Node and XVFB
- image: beneaththeink/node-xvfb:6
# - image: node:6.5.0
# the Docker image with Cypress dependencies and Chrome browser
- image: cypress/internal:chrome58
environment:
npm_config_loglevel: warn
# even when running as non-root user
# need to set unsafe perm to be able to do `npm postinstall`
npm_config_unsafe-perm: true
working_directory: ~/cypress-monorepo
parallelism: 3
steps:
@@ -13,110 +17,111 @@ jobs:
# need to restore a separate cache for each package.json
- restore_cache:
key: v1-root-deps
key: v3-{{ .Branch }}-root-deps
- restore_cache:
key: v1-deps-coffee
key: v3-{{ .Branch }}-deps-coffee
- restore_cache:
key: v1-deps-desktop-gui
key: v3-{{ .Branch }}-deps-desktop-gui
- restore_cache:
key: v1-deps-driver
key: v3-{{ .Branch }}-deps-driver
- restore_cache:
key: v1-deps-example
key: v3-{{ .Branch }}-deps-example
- restore_cache:
key: v1-deps-electron
key: v3-{{ .Branch }}-deps-electron
- restore_cache:
key: v1-deps-extension
key: v3-{{ .Branch }}-deps-extension
- restore_cache:
key: v1-deps-https-proxy
key: v3-{{ .Branch }}-deps-https-proxy
- restore_cache:
key: v1-deps-launcher
key: v3-{{ .Branch }}-deps-launcher
- restore_cache:
key: v1-deps-reporter
key: v3-{{ .Branch }}-deps-reporter
- restore_cache:
key: v1-deps-runner
key: v3-{{ .Branch }}-deps-runner
- restore_cache:
key: v1-deps-server
key: v3-{{ .Branch }}-deps-server
- restore_cache:
key: v1-deps-socket
key: v3-{{ .Branch }}-deps-socket
- restore_cache:
key: v1-deps-static
key: v3-{{ .Branch }}-deps-static
- restore_cache:
key: v1-deps-ts
key: v3-{{ .Branch }}-deps-ts
- restore_cache:
key: v1-deps-docs
key: v3-{{ .Branch }}-deps-docs
# why is the `npm install` on CircleCI so verbose?
- run: echo 'loglevel=warn' >> ~/.npmrc
- run: npm install
- run: npm run all install
- run: cd docs && npm install
# only installs the root dependencies, without going into packages
# via postinstall script
- run: npm install --ignore-scripts
- run: npm run install-packages-serial
# save each node_modules folder per package
- save_cache:
key: v1-root-deps-{{ checksum "package.json" }}
key: v3-{{ .Branch }}-root-deps-{{ checksum "package.json" }}
paths:
- node_modules
- save_cache:
key: v1-deps-coffee-{{ checksum "packages/coffee/package.json" }}
key: v3-{{ .Branch }}-deps-coffee-{{ checksum "packages/coffee/package.json" }}
paths:
- packages/coffee/node_modules
- save_cache:
key: v1-deps-desktop-gui-{{ checksum "packages/desktop-gui/package.json" }}
key: v3-{{ .Branch }}-deps-desktop-gui-{{ checksum "packages/desktop-gui/package.json" }}
paths:
- packages/desktop-gui/node_modules
- save_cache:
key: v1-deps-driver-{{ checksum "packages/driver/package.json" }}
key: v3-{{ .Branch }}-deps-driver-{{ checksum "packages/driver/package.json" }}
paths:
- packages/driver/node_modules
- save_cache:
key: v1-deps-example-{{ checksum "packages/example/package.json" }}
key: v3-{{ .Branch }}-deps-example-{{ checksum "packages/example/package.json" }}
paths:
- packages/example/node_modules
- save_cache:
key: v1-deps-electron-{{ checksum "packages/electron/package.json" }}
key: v3-{{ .Branch }}-deps-electron-{{ checksum "packages/electron/package.json" }}
paths:
- packages/electron/node_modules
- save_cache:
key: v1-deps-extension-{{ checksum "packages/extension/package.json" }}
key: v3-{{ .Branch }}-deps-extension-{{ checksum "packages/extension/package.json" }}
paths:
- packages/extension/node_modules
- save_cache:
key: v1-deps-https-proxy-{{ checksum "packages/https-proxy/package.json" }}
key: v3-{{ .Branch }}-deps-https-proxy-{{ checksum "packages/https-proxy/package.json" }}
paths:
- packages/https-proxy/node_modules
- save_cache:
key: v1-deps-launcher-{{ checksum "packages/launcher/package.json" }}
key: v3-{{ .Branch }}-deps-launcher-{{ checksum "packages/launcher/package.json" }}
paths:
- packages/launcher/node_modules
- save_cache:
key: v1-deps-reporter-{{ checksum "packages/reporter/package.json" }}
key: v3-{{ .Branch }}-deps-reporter-{{ checksum "packages/reporter/package.json" }}
paths:
- packages/reporter/node_modules
- save_cache:
key: v1-deps-runner-{{ checksum "packages/runner/package.json" }}
key: v3-{{ .Branch }}-deps-runner-{{ checksum "packages/runner/package.json" }}
paths:
- packages/runner/node_modules
- save_cache:
key: v1-deps-server-{{ checksum "packages/server/package.json" }}
key: v3-{{ .Branch }}-deps-server-{{ checksum "packages/server/package.json" }}
paths:
- packages/server/node_modules
- save_cache:
key: v1-deps-socket-{{ checksum "packages/socket/package.json" }}
key: v3-{{ .Branch }}-deps-socket-{{ checksum "packages/socket/package.json" }}
paths:
- packages/socket/node_modules
- save_cache:
key: v1-deps-static-{{ checksum "packages/static/package.json" }}
key: v3-{{ .Branch }}-deps-static-{{ checksum "packages/static/package.json" }}
paths:
- packages/static/node_modules
- save_cache:
key: v1-deps-ts-{{ checksum "packages/ts/package.json" }}
key: v3-{{ .Branch }}-deps-ts-{{ checksum "packages/ts/package.json" }}
paths:
- packages/ts/node_modules
- save_cache:
key: v1-deps-docs-{{ checksum "docs/package.json" }}
key: v3-{{ .Branch }}-deps-docs-{{ checksum "docs/package.json" }}
paths:
- docs/node_modules
- run: npm run build
#
# things to run in the 1st CI container
#
@@ -150,25 +155,6 @@ jobs:
#
# things to run in the 2nd CI container
#
- run:
name: Docs - innstalling dependencies
command: |
if [ $CIRCLE_NODE_INDEX == 1 ]; then
cd docs
npm install
npm run build
fi
- run:
name: Docs - verifying Cypress
command: |
if [ $CIRCLE_NODE_INDEX == 1 ]; then
set -o xtrace
cd docs
npm run precypress
$(npm bin)/cypress --help
$(npm bin)/cypress --version
$(npm bin)/cypress verify
fi
- run:
name: Docs - running E2E tests
command: |
@@ -183,5 +169,16 @@ jobs:
name: Driver unit tests
command: |
if [ $CIRCLE_NODE_INDEX == 2 ]; then
npm run all test -- --package driver
chrome --version
xvfb-run -as "-screen 0 1280x720x16" npm run all test -- --package driver
fi
#
# things to run in the 4th CI container
#
# - run:
# name: Example e2e tests
# command: |
# if [ $CIRCLE_NODE_INDEX == 3 ]; then
# # ./bin/cypress --project=./packages/example --path-to-cypress
# # xvfb-run -as "-screen 0 1280x720x16" npm start -- --project=./packages/example
# fi
+2 -2
View File
@@ -110,7 +110,7 @@ hfc_html:
enable: true
exclude:
hfc_css:
enable: false
enable: true
exclude:
- '*.min.css'
hfc_js:
@@ -134,7 +134,7 @@ hfc_img:
jpegrecompress: false
jpegrecompressQuality: 'medium'
optipng: true
svgo: true
svgo: false
hfc_favicons:
enable: false
+13 -13
View File
@@ -8,7 +8,7 @@ const revisionOpts = {
dontRenameFile: ['.html', 'CNAME'],
dontUpdateReference: ['.html'],
dontSearchFile: ['.js'],
debug: true
debug: process.env.NODE_ENV === 'production'
}
function remove (folder) {
@@ -46,27 +46,27 @@ gulp.task('move:doc:search:css', function () {
})
// move font files
gulp.task('move:fira:fonts', function () {
return gulp
gulp.task('move:fira:fonts', function () {
return gulp
.src('./node_modules/fira/**')
.pipe(gulp.dest('./themes/cypress/source/fonts/vendor/fira'))
})
})
gulp.task('move:font:awesome:fonts', (cb) => {
runSequence('move:font:awesome:css', 'move:font:awesome:fonts:folder', cb)
})
gulp.task('move:font:awesome:fonts', (cb) => {
runSequence('move:font:awesome:css', 'move:font:awesome:fonts:folder', cb)
})
gulp.task('move:font:awesome:css', function () {
return gulp
gulp.task('move:font:awesome:css', function () {
return gulp
.src('./node_modules/font-awesome/css/font-awesome.css')
.pipe(gulp.dest('./themes/cypress/source/fonts/vendor/font-awesome/css'))
})
})
gulp.task('move:font:awesome:fonts:folder', function () {
return gulp
gulp.task('move:font:awesome:fonts:folder', function () {
return gulp
.src('./node_modules/font-awesome/fonts/*')
.pipe(gulp.dest('./themes/cypress/source/fonts/vendor/font-awesome/fonts'))
})
})
gulp.task('revision', () => {
return gulp
+2 -2
View File
@@ -6,14 +6,14 @@
"version": "3.3.1"
},
"scripts": {
"postinstall": "npm run build",
"postinstall": "echo 'This project needs: npm run build'",
"prebuild": "npm run clean",
"build": "hexo generate",
"postbuild": "gulp post:build",
"clean": "hexo clean",
"clean-deps": "rm -rf node_modules",
"convert": "node ./cy_scripts/convert.js",
"deploy": "npm run build && hexo deploy",
"deploy": "NODE_ENV=production npm run build && hexo deploy",
"start": "hexo server --port 2222",
"precypress": "npm install cypress-cli && cypress install",
"cypress": "cypress run --record --key $DOCS_RECORD_KEY",
+7
View File
@@ -0,0 +1,7 @@
/* global hexo */
// only run the filter_cleanup if we are in
// production mode -- deploying static asset
if (process.env.NODE_ENV !== 'production') {
hexo.config.filter_cleanup = false
}
+20 -4
View File
@@ -190,12 +190,28 @@ cy.get("body").type("{uparrow}{uparrow}{downarrow}{downarrow}{leftarrow}{rightar
cy.get("body").type("{shift}", {release: false}).get("li:first").click()
```
# Date inputs
Using `cy.type()` on a date input (`<input type="date">`) requires specifying a valid date in the format `yyyy-MM-dd`, e.g. `1999-12-31`. This isn't exactly how a user would type into a date input, but is a workaround since date input support varies between browsers and the format varies based on locale. `yyyy-MM-dd` is the format required by [the W3 spec](https://www.w3.org/TR/html/infrastructure.html#sec-dates) and is what the input's `value` will be set to regardless of browser or locale. Special characters (`{leftarrow}`, `{selectall}`, etc) are not permitted.
# Month inputs
Using `cy.type()` on a month input (`<input type="month">`) requires specifying a valid month in the format `yyyy-MM`, e.g. `1999-12`. This isn't exactly how a user would type into a month input, but is a workaround since month input support varies between browsers and the format varies based on locale. `yyyy-MM` is the format required by [the W3 spec](https://www.w3.org/TR/html/infrastructure.html#months) and is what the input's `value` will be set to regardless of browser or locale. Special characters (`{leftarrow}`, `{selectall}`, etc) are not permitted.
# Week inputs
Using `cy.type()` on a week input (`<input type="week">`) requires specifying a valid week in the format `yyyy-Www`, where `W` is the literal character 'W' and `ww` is the number of the week (01-53), e.g. `1999-W23` (23rd week of 1999). This isn't exactly how a user would type into a week input, but is a workaround since week input support varies between browsers and the format varies based on locale. `yyyy-Www` is the format required by [the W3 spec](https://www.w3.org/TR/html/infrastructure.html#valid-week-string) and is what the input's `value` will be set to regardless of browser or locale. Special characters (`{leftarrow}`, `{selectall}`, etc) are not permitted.
# Time inputs
Using `cy.type()` on a time input (`<input type="time">`) requires specifying a valid time in the format `HH:mm`, `HH:mm:ss`, or `HH:mm:ss.SSS`, where `HH` is 00-23, `mm` is 00-59, `ss` is 00-59, and `SSS` is 000-999. Special characters (`{leftarrow}`, `{selectall}`, etc) are not permitted. The following are examples of valid times:
* 01:30
* 23:15
* 12:00:00.384
# Known Issues
## Native `input[type=date,datetime,datetime-local,month,year,color]`
Special input types are *not* supported yet because browsers implement these input types outside of what is accessible to JavaScript. They also depend on OS regional settings. The fix however is relatively simple - Cypress will require you to type the final *formatted* value that the input will be set to - and then all will work. [Open an issue](https://github.com/cypress-io/cypress/issues/new?body=**Description**%0A*Include%20a%20high%20level%20description%20of%20the%20error%20here%20including%20steps%20of%20how%20to%20recreate.%20Include%20any%20benefits%2C%20challenges%20or%20considerations.*%0A%0A**Code**%0A*Include%20the%20commands%20used*%0A%0A**Steps%20To%20Reproduce**%0A-%20%5B%20%5D%20Steps%0A-%20%5B%20%5D%20To%0A-%20%5B%20%5D%20Reproduce%2FFix%0A%0A**Additional%20Info**%0A*Include%20any%20images%2C%20notes%2C%20or%20whatever.*%0A) if you need this to be fixed.
## Typing `tab` key does not work
Tabbing will be implemented as a separate command as `cy.tab` and support things like multiple tabs, tabbing in reverse, or tabbing to a specific element. [Open an issue](https://github.com/cypress-io/cypress/issues/new?body=**Description**%0A*Include%20a%20high%20level%20description%20of%20the%20error%20here%20including%20steps%20of%20how%20to%20recreate.%20Include%20any%20benefits%2C%20challenges%20or%20considerations.*%0A%0A**Code**%0A*Include%20the%20commands%20used*%0A%0A**Steps%20To%20Reproduce**%0A-%20%5B%20%5D%20Steps%0A-%20%5B%20%5D%20To%0A-%20%5B%20%5D%20Reproduce%2FFix%0A%0A**Additional%20Info**%0A*Include%20any%20images%2C%20notes%2C%20or%20whatever.*%0A) if you need this to be fixed.
+111 -32
View File
@@ -10,29 +10,32 @@ containerClass: faq
## What is Cypress?
## Hasnt this been done before?
## Is Cypress free?
Cypress desktop app and CLI are free to use. The Cypress Dashboard is a premium feature for non-open source projects and offers recording videos, screenshots and logs in a web interface.
## What operating systems do you support?
The desktop application can be installed in OSX and Linux. Windows is not yet supported, although you can use Cypress if you install a Linux VM using something like VirtualBox or using a Docker image.
## Do you support native mobile apps?
Cypress would never be able to run on a native mobile app, but would be able to run in a web view. In that mode, you'd see the commands display in a browser while you would drive the mobile device separately. Down the road we'll likely have first class support for this, but today it is not a current priority.
Currently you can control the [`.viewport()`](https://on.cypress.io/viewport) to test responsive, mobile views in a website or web application.
## Do you support X language or X framework?
Cypress tests anything that runs in the context of a browser. It is backend, front-end, language and framework agnostic.
Your actual test code, however does need to be written in JavaScript (or a language that transpiles into JavaScript).
## Will Cypress work in my CI provider?
Cypress works in any CI provider.
## What are good use cases for Cypress?
@@ -42,18 +45,20 @@ containerClass: faq
## Is there code coverage?
[#346](https://github.com/cypress-io/cypress/issues/346)
There is nothing currently built into Cypress to do this. Adding code coverage around end to end tests is much harder than unit and its possible it may not be feasible to do in a generic way. You can read in more detail about code coverage [here](https://github.com/cypress-io/cypress/issues/346).
## What kind of tests do I write in Cypress?
## Does Cypress use Selenium / Webdriver?
No. In fact Cypress' architecture is very different from Selenium in a few critical ways:
- Cypress runs in the context of the browser. With Cypress it's much easier to accurately test the browser, but harder to talk to the outside work. In Selenium it's the exact opposite. Although Cypress has a few commands that give you access to the outside world - like [.request()](http://on.cypress.io/request) and [.exec()](https://on.cypress.io/exec).
## Are there driver bindings in my language?
Cypress does *not* utilize WebDriver for testing, so does not use or have any notion of driver bindings.
## Does Cypress have an equivalent to Selenium IDE?
@@ -68,9 +73,11 @@ containerClass: faq
## I found a bug! What do I do?
- Search existing [open issues](https://github.com/cypress-io/cypress/issues), it may already be reported!
- Update Cypress. Your issue may have [already been fixed](https://github.com/cypress-io/cypress/wiki/changelog).
- [Open an issue](https://github.com/cypress-io/cypress/issues/new). Your best chance of getting a bug looked at quickly is to provide a repository with a reproducible bug that can be cloned and run.
# Using Cypress (potentially split this into Beginner / Advanced sections)
# Using Cypress
## How do I wait for an element not to exist?
@@ -81,25 +88,38 @@ containerClass: faq
## How can I parallelize my runs?
[#64](https://github.com/cypress-io/cypress/issues/64)
You can read more about parallelization [here](https://github.com/cypress-io/cypress/issues/64).
## Can I run a single test or group of tests?
[#236](https://github.com/cypress-io/cypress/issues/263)
You can run a group of tests or a single test by placing an `.only` to a test suite or specific test.
You can run a single test headlessly by passing the `--spec` flag to `cypress run`.
Currently there is no way to specify a group of tests to run headlessly. You can read more [here](https://github.com/cypress-io/cypress/issues/263).
## How do I test uploading a file?
[#170](https://github.com/cypress-io/cypress/issues/170)
It is possible to upload files in your application but its different based on how you've written your own upload code. You can read more about this [here](https://github.com/cypress-io/cypress/issues/170)
## What is the projectId for?
A `projectId` is added to your `cypress.json` after you setup your project for the Dashboard. This `cypress.json` is meant to be checked into source control.
The `projectId` identifies your project in the [Dashboard](https://on.cypress.io/dashboard), which gives you valuable insight into CI runs and failure and debugging info.
You can forgo the projectId and CI keys altogether by running `cypress run` headlessly, but then you're basically opting out of the Dashboard and all its goodies.
## How do I get the native DOM reference of an element found using Cypress?
Cypress wraps elements in jQuery so you'd just get the native element from there.
```javascript
cy.get('button').then(($el) => {
$el.get(0)
})
```
## How do I make Cypress wait for an XHR request?
@@ -107,74 +127,131 @@ containerClass: faq
## How do I wait for multiple XHR requests to the same url?
You should set up an alias (using [`.as()`](https://on.cypress.io/api/as)) to a single route that matches all of the XHRs. You can then [`.wait()`](https://on.cypress.io/wait) on it multiple times and Cypress keeps track of how many matching XHR requests there are.
```javascript
cy.server()
cy.route('users').as('getUsers')
cy.wait('@getUsers') // Wait for first GET to /users/
cy.get('#list>li').should('have.length', 10)
cy.get('#load-more-btn').click()
cy.wait('@getUsers') // Wait for second GET to /users/
cy.get('#list>li').should('have.length', 20)
```
## How do I test drag-n-drop?
<!-- ## How do I test drag-n-drop? -->
## How do I seed / reset my database?
You can use either [`.request()`](https://on.cypress.io/request) or [`cy.exec`](https://on.cypress.io/exec) to talk to your backend to seed data.
You could also just stub XHR requests directly using [`.route()`](https://on.cypress.io/route) which avoids ever even needing to fuss with your database.
## How do I pass data to my webserver from Cypress?
## How do I content inside an iframe?
## How do I test content inside an iframe?
[#136](https://github.com/cypress-io/cypress/issues/136)
Currently Cypress does not support selecting or accessing elements from within an iframe. You can read more about this [#here](https://github.com/cypress-io/cypress/issues/136).
## How do I preserve cookies/localstorage in between my tests?
[#461](https://github.com/cypress-io/cypress/issues/461)
By default, Cypress automatically clears all cookies **before** each test to prevent state from building up.
You can whitelist specific cookies to be preserved across tests:
```javascript
// now any cookie with the name 'session_id' will
// not be cleared before each test runs
Cypress.Cookies.defaults({
whitelist: "session_id"
})
```
You cannot currently preserve localStorage across tests and can read more [here](https://github.com/cypress-io/cypress/issues/461).
## Some of my elements animate in, how do I work around that?
Oftentimes you can usually account for animation by asserting `.should('be.visible')` or another assertion on one of the elements you expect to be animated in.
```javascript
// assuming a click event causes the animation
cy.get('element').click().should('not.have.class', 'animating')
```
If the animation is especially long, you could extend the time Cypress waits for the assertion to be true by increasing the `timeout`.
```javascript
cy.get('button', { timeout: 10000 }) // <-- wait up to 10 seconds for this 'button' to be found
.should('be.visible') // <-- and to be visible
cy.get('element').click({ timeout: 10000 }).should('not.have.class', 'animating')
```
## Can I test anchor links that open in a new tab?
Cypress does not and may never have multi-tab support for various reasons.
Luckily there are lots of easy and safe workarounds that enable you to test the behavior of your application
## Should I start my webserver from within Cypress?
## Can I make an assertion on my applications console.logs?
[Read through this recipe to see how to test anchor links.](https://github.com/cypress-io/cypress-example-recipes/blob/master/cypress/integration/tab_handling_anchor_links_spec.js)
## How do I run my tests in another browser?
## Where do I get the key to run my tests in CI?
## Can I create more than one key for CI?
## I have an app that needs to be tested across multiple user sessions, like a chat app across 2 browsers. How do I test that?
## I want to test clicking a link that navigates, how do I wait and check the resulting location url?
## Is there a way to watch for an xhr request and assert that the response code came back a certain way?
## Im running a lot of tests that appear to slow down as they run, is there a way to fix this?
## How do I get an input's value in Cypress?
Cypress DOM elements are just jQuery elements so you can use any method available in jQuery. Below are some examples of working with an input's value.
```javascript
cy.get('input').invoke('val').then((val) => {
// do something with value here
})
cy.get('input').then(($input) => {
// do something with value here
$input.val()
})
// make an assertion on the value
cy.get('input').should('have.value', 'abc')
```
## How do I make conditional based assertions / control flow?
## How do I require "" node module in Cypress?
The code you write in Cypress is executed in the browser, so you can import or require JS modules, but only those that work in the browser.
Cypress doesn't have direct access to node or your file system. We recommend utilizing [`.exec()`](https://on.cypress.io/exec) to execute a shell command or a node script that will do what you need.
## Is there a way to give a proper SSL certificate to your proxy so the page doesn't show up as "not secure"?
No, Cypress modifies network traffic in real time and therefore must sit between your server and the browser. There is no other way for us to achieve that.
## Can I use the Page Object pattern?
As far as page objects are concerned, you should be able to use regular JavaScript functions and aliasing with [`.as()`](https://on.cypress.io/as) to essentially recreate what page objects give you.
# Dashboard
@@ -225,6 +302,8 @@ containerClass: faq
## Whos behind Cypress?
You can read more about who's behind Cypress on our [here](https://www.cypress.io/about).
## Are you hiring?
You can check our open positions [here](https://www.cypress.io/jobs).
+3 -2
View File
@@ -22,7 +22,8 @@ a code {
}
}
.article pre, .article code {
.article pre, .article code,
.faq pre, .faq code {
&[class*="language-"] {
font-size: 0.9em;
line-height: $line-height;
@@ -58,7 +59,7 @@ a code {
}
}
.article pre {
.article pre, .faq pre {
&[class*="language-"] {
border-left: 5px solid $color-border;
padding: 0.5em 0.25em;
+1 -1
View File
@@ -47,7 +47,7 @@
}
}
.article-content {
.article-content, .faq {
line-height: $line-height;
p {
-29
View File
@@ -1,29 +0,0 @@
/* eslint-disable no-console */
// http://stackoverflow.com/questions/1683531/how-to-import-existing-git-repository-into-another#answer-8396318
const { snakeCase } = require('lodash')
const { execSync } = require('child_process')
const path = require('path')
const argv = require('minimist')(process.argv.slice(2))
const from = argv.from
const to = path.join(argv.to, '/') // ensure trailing slash
const branch = argv.branch || 'master'
const remoteName = snakeCase(to)
function exec (command) {
console.log(command)
execSync(command, { stdio: 'inherit' })
}
console.log()
console.log(`Importing ${from} (${branch}) to ${to}`)
console.log('---------')
exec(`git remote add ${remoteName} ${from}`)
exec(`git fetch ${remoteName}`)
exec(`git merge -s ours --allow-unrelated-histories --no-commit ${remoteName}/${branch}`)
exec(`git read-tree --prefix=${to} -u ${remoteName}/${branch}`)
exec(`git commit -m "import ${from} (${branch}) to ${to}"`)
console.log('---------')
console.log(`Finished importing ${from} (${branch}) to ${to}`)
-1
View File
@@ -126,7 +126,6 @@ const logErr = (err) => {
}
const logFinish = (options) => {
// console.error('--- log finish')
/* eslint-disable no-console */
console.log(
chalk.white(' -'),
-1
View File
@@ -1,5 +1,4 @@
const _ = require('lodash')
const chalk = require('chalk')
const downloadUtils = require('../download/utils')
const spawn = require('./spawn')
-20
View File
@@ -1,20 +0,0 @@
/* eslint-disable no-console */
// http://stackoverflow.com/questions/1683531/how-to-import-existing-git-repository-into-another#answer-8396318
const { snakeCase } = require('lodash')
const { execSync } = require('child_process')
const path = require('path')
const argv = require('minimist')(process.argv.slice(2))
const to = path.join(argv._[0], '/') // ensure trailing slash
const branch = argv._[1] || 'master'
const remote = snakeCase(to)
const command = `git subtree pull --prefix=${to} ${remote} ${branch}`
console.log(`Merging remote ${remote} (${branch}) to ${to}`)
console.log('---------')
console.log(command)
execSync(command, { stdio: 'inherit' })
console.log('---------')
console.log(`Finished erging remote ${remote} (${branch}) to ${to}`)
+4 -1
View File
@@ -13,7 +13,10 @@
"test": "echo 'This runs just the CLI tests' && mocha",
"test-watch": "mocha --watch",
"test-e2e": "blah",
"postinstall": "npm run all install"
"install-packages": "npm run all install",
"install-packages-serial": "npm run all install -- --serial",
"postinstall": "npm run install-packages && npm run build",
"clean-deps": "npm run all clean-deps"
},
"author": "",
"license": "MIT",
+1 -1
View File
@@ -4,7 +4,7 @@
"description": "Desktop GUI for managing Cypress projects.",
"main": "lib/gui.js",
"scripts": {
"postinstall": "npm run build",
"postinstall": "echo 'This project needs: npm run build'",
"build": "zunder build-dev",
"build-prod": "node ./scripts/build-prod.js",
"run-prod": "npm run build-prod && npm run server",
+4 -2
View File
@@ -3,12 +3,13 @@
"version": "1.0.0",
"description": "",
"scripts": {
"postinstall": "npm run build",
"postinstall": "echo 'This project needs: npm run build'",
"build": "gulp build",
"watch": "gulp watch",
"clean-deps": "rm -rf node_modules",
"test": "gulp test",
"test-watch": "gulp test:watch"
"test-watch": "gulp test:watch",
"watch-test": "gulp test:watch"
},
"devDependencies": {
"@cypress/bower-kendo-ui": "0.0.2",
@@ -27,6 +28,7 @@
"coffeeify": "^2.1.0",
"compression": "^1.1.0",
"cors": "^2.7.1",
"debug": "^2.6.8",
"errorhandler": "^1.1.1",
"express": "^4.12.3",
"fs-extra": "^3.0.0",
@@ -1,6 +1,7 @@
_ = require("lodash")
$ = require("jquery")
Promise = require("bluebird")
moment = require("moment")
{ delay, waitForAnimations } = require("./utils")
$Log = require("../../../cypress/log")
@@ -9,6 +10,10 @@ utils = require("../../../cypress/utils")
inputEvents = "textInput input".split(" ")
textLike = "textarea,:text,[contenteditable],[type=password],[type=email],[type=number],[type=date],[type=week],[type=month],[type=time],[type=datetime],[type=datetime-local],[type=search],[type=url],[type=tel]"
dateRegex = /^\d{4}-\d{2}-\d{2}$/
monthRegex = /^\d{4}-(0\d|1[0-2])$/
weekRegex = /^\d{4}-W(0[1-9]|[1-4]\d|5[0-3])$/
timeRegex = /^([0-1]\d|2[0-3]):[0-5]\d(:[0-5]\d)?(\.[0-9]{1,3})?$/
module.exports = (Cypress, Commands) ->
Cypress.on "test:before:run", ->
@@ -73,6 +78,10 @@ module.exports = (Cypress, Commands) ->
isBody = options.$el.is("body")
isTextLike = options.$el.is(textLike)
isDate = options.$el.is("[type=date]")
isTime = options.$el.is("[type=time]")
isMonth = options.$el.is("[type=month]")
isWeek = options.$el.is("[type=week]")
hasTabIndex = options.$el.is("[tabindex]")
## TODO: tabindex can't be -1
@@ -102,6 +111,43 @@ module.exports = (Cypress, Commands) ->
if _.isBlank(chars)
utils.throwErrByPath("type.empty_string", { onFail: options._log })
if isDate and (
not _.isString(chars) or
not dateRegex.test(chars) or
not moment(chars).isValid()
)
utils.throwErrByPath("type.invalid_date", {
onFail: options._log
args: { chars }
})
if isMonth and (
not _.isString(chars) or
not monthRegex.test(chars)
)
utils.throwErrByPath("type.invalid_month", {
onFail: options._log
args: { chars }
})
if isWeek and (
not _.isString(chars) or
not weekRegex.test(chars)
)
utils.throwErrByPath("type.invalid_week", {
onFail: options._log
args: { chars }
})
if isTime and (
not _.isString(chars) or
not timeRegex.test(chars)
)
utils.throwErrByPath("type.invalid_time", {
onFail: options._log
args: { chars }
})
options.chars = "" + chars
type = =>
@@ -166,6 +212,16 @@ module.exports = (Cypress, Commands) ->
return dispatched
needSingleValueChange = ->
isDate or
isMonth or
isWeek or
isTime or
(options.$el.is("[type=number]") and _.includes(options.chars, "."))
## see comment in updateValue below
typed = ""
$Keyboard.type({
$el: options.$el
chars: options.chars
@@ -173,6 +229,18 @@ module.exports = (Cypress, Commands) ->
release: options.release
window: @privateState("window")
updateValue: (rng, key) ->
if needSingleValueChange()
## in these cases, the value must only be set after all
## the characters are input because attemping to set
## a partial/invalid value results in the value being
## set to an empty string
typed += key
if typed is options.chars
options.$el.val(options.chars)
else
rng.text(key, "end")
onBeforeType: (totalKeys) =>
## for the total number of keys we're about to
## type, ensure we raise the timeout to account
@@ -1,4 +1,5 @@
_ = require("lodash")
Promise = require("bluebird")
$Location = require("../../cypress/location")
$Log = require("../../cypress/log")
@@ -119,7 +120,6 @@ module.exports = (Cypress, Commands) ->
## if we made a request prior to a visit then it needs
## to be filled out
if not $Location.isFullyQualifiedUrl(options.url)
debugger
utils.throwErrByPath("request.url_invalid")
## only set json to true if form isnt true
@@ -1,4 +1,5 @@
_ = require("lodash")
Promise = require("bluebird")
$Cy = require("../../cypress/cy")
$Location = require("../../cypress/location")
+2 -2
View File
@@ -295,7 +295,7 @@ $Cypress.Commands = $Commands
$Cypress.Config = $Config
$Cypress.Cookies = $Cookies
$Cypress.Cy = $Cy
$Cypress.Dom = $Dom
$Cypress.Dom = $Cypress.prototype.Dom = $Dom
$Cypress.EnvironmentVariables = $EnvironmentVariables
$Cypress.ErrorMessages = $ErrorMessages
$Cypress.Keyboard = $Keyboard
@@ -304,7 +304,7 @@ $Cypress.Location = $Location
$Cypress.LocalStorage = $LocalStorage
$Cypress.Mocha = $Mocha
$Cypress.Runner = $Runner
$Cypress.Server = $Server
$Cypress.Server = $Cypress.prototype.Server = $Server
$Cypress.utils = utils
## expose globally (temporarily for the runner)
@@ -588,6 +588,10 @@ module.exports = {
type:
empty_string: "#{cmd('type')} cannot accept an empty String. You need to actually type something."
invalid: "Special character sequence: '{{chars}}' is not recognized. Available sequences are: {{allChars}}"
invalid_date: "Typing into a date input with #{cmd('type')} requires a valid date with the format 'yyyy-MM-dd'. You passed: {{chars}}"
invalid_month: "Typing into a month input with #{cmd('type')} requires a valid month with the format 'yyyy-MM'. You passed: {{chars}}"
invalid_week: "Typing into a week input with #{cmd('type')} requires a valid week with the format 'yyyy-Www', where W is the literal character 'W' and ww is the week number (00-53). You passed: {{chars}}"
invalid_time: "Typing into a time input with #{cmd('type')} requires a valid time with the format 'HH:mm', 'HH:mm:ss' or 'HH:mm:ss.SSS', where HH is 00-23, mm is 00-59, ss is 00-59, and SSS is 000-999. You passed: {{chars}}"
multiple_elements: "#{cmd('type')} can only be called on a single textarea or :text. Your subject contained {{num}} elements."
not_on_text_field: "#{cmd('type')} can only be called on textarea or :text. Your subject is a: {{node}}"
tab: "{tab} isn't a supported character sequence. You'll want to use the command #{cmd('tab')}, which is not ready yet, but when it is done that's what you'll use."
+1 -4
View File
@@ -475,9 +475,6 @@ $Keyboard = {
return dispatched
updateValue: (rng, key) ->
rng.text(key, "end")
typeKey: (el, key, options) ->
## if we have an afterKey value it means
## we've typed in prior to this
@@ -489,7 +486,7 @@ $Keyboard = {
@moveCaretToEnd(options.rng)
@ensureKey el, key, options, ->
@updateValue(options.rng, key)
options.updateValue(options.rng, key)
ensureKey: (el, key, options, fn) ->
options.id = _.uniqueId("char")
+1
View File
@@ -1,3 +1,4 @@
$ = require("jquery")
_ = require("lodash")
Backbone = require("backbone")
+1
View File
@@ -1,3 +1,4 @@
$ = require("jquery")
_ = require("lodash")
errorMessages = require("./error_messages")
@@ -44,6 +44,10 @@ args = [
"--disable-component-update"
"--disable-default-apps"
# Run Chrome with options to work inside Docker container
"--no-sandbox"
"--disable-gpu"
"--load-extension=#{themeDir}"
"--user-data-dir=#{profileDir}"
]
@@ -125,13 +125,10 @@
el with tabindex
</div>
<input id="input" />
<input id="input-with-value" value="foo" />
<input id="number-with-value" value="12" />
<input id="email-with-value" value="brian@foo.c" />
<button id="button">button</button>
<form id="by-id">
<input id="input" />
<input id="name" />
<input id="age" />
</form>
@@ -257,11 +254,30 @@
</div>
<div id="input-types">
<input type="number" />
<input type="email" />
<input type="password" />
<input type="date" />
<input type="time" />
<input id="input-with-value" value="foo" />
<input id="input-without-value" />
<input id="number-with-value" type="number" value="12" />
<input id="number-without-value" type="number" />
<input id="email-with-value" type="email" value="brian@foo.c" />
<input id="email-without-value" type="email" />
<input id="password-with-value" type="password" value="pass" />
<input id="password-without-value" type="password" />
<input id="date-with-value" type="date" value="2016-01-01" />
<input id="date-without-value" type="date" />
<input id="month-with-value" type="month" value="2017-05" />
<input id="month-without-value" type="month" />
<input id="week-with-value" type="week" value="2017-W05" />
<input id="week-without-value" type="week" />
<input id="time-with-value" type="time" value="01:23:45" />
<input id="time-without-value" type="time" />
<div contenteditable="true"></div>
<textarea></textarea>
</div>
@@ -4,6 +4,7 @@ chalk = require("chalk")
EventEmitter = require("events").EventEmitter
path = require("path")
Promise = require("bluebird")
log = require("debug")("cypress:driver")
browser = require("./browser")
SocketServer = require("socket.io")
@@ -83,6 +84,7 @@ module.exports = class Runner
@_runnerBus.emit(event, info, err)
runAllSpecsOnce: (specPaths) ->
log "running all specs once"
@_start()
.then =>
new Promise (resolve, reject) =>
@@ -137,6 +139,7 @@ module.exports = class Runner
client.emit("run", specPath)
_start: ->
log "Starting runner"
io = new SocketServer(@_server)
io.on "connection", (client) =>
@_handleConnection(client)
@@ -156,6 +159,7 @@ module.exports = class Runner
delete @_clients[client.id]
_launchBrowser: ->
log "launching browser"
theBrowser = getArg("browser") or "chrome"
url = "http://localhost:#{@_config.port}?reporter=socket"
+12 -11
View File
@@ -3,19 +3,20 @@ lolex = require("lolex")
sinon = require("sinon")
sinonChai = require("sinon-chai")
window.$Cypress = $Cypress = require("../../src/main")
$Cypress = require("../../src/main")
$ = $Cypress.$
_ = $Cypress.prototype._
$ = window.$ = $Cypress.$
_ = window._ = $Cypress.prototype._
window.moment = $Cypress.prototype.moment
window.Promise = $Cypress.prototype.Promise
window.Cookies = require("js-cookie")
window.testUtils = {
$Cypress: $Cypress
$: $
_: _
moment: $Cypress.prototype.moment
Promise: $Cypress.prototype.Promise
Cookies: require("js-cookie")
bililiteRange: require("../../vendor/bililiteRange")
}
## TODO: move this as something we can grab
## off of the driver
require("sinon-as-promised")(Promise)
window.bililiteRange = require("../../vendor/bililiteRange")
$Cypress.Chai.use(sinonChai)
uncaught = Mocha.Runner::uncaught
@@ -1,21 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Test .only Fixture</title>
<script type="text/javascript">
window.mocha = new parent.Mocha({
reporter: function(){}
})
</script>
<script type="text/javascript" src="/lib/public/js/iframe.js"></script>
<script type="text/javascript">
context("suite1 [AbC]", function(){
it("test2 [656]", function(){})
})
</script>
</head>
<body></body>
</html>
@@ -1,79 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Test .only Fixture</title>
<script type="text/javascript">
window.mocha = new parent.Mocha({
reporter: function(){}
})
</script>
<script type="text/javascript" src="/lib/public/js/iframe.js"></script>
<script type="text/javascript">
// before(function(){
// debugger
// console.log("root beforeAll")
// })
// after(function(){
// debugger
// })
describe("1suite [s01]", function(){
// before(function(){
// console.log("suite before")
// })
// beforeEach(function(){
// console.log("suite beforeEach")
// })
// after(function(){
// debugger
// })
it("test [t01]", function(){})
it("test [t02]", function(){})
it("test [t03]", function(){})
context("2nested suite [s02]", function(){
// afterEach(function(){
// debugger
// })
// after(function(){
// debugger
// })
it("test [t04]", function(){})
context("3nested suite [s03]", function(){
it("test [t05]", function(){})
it("test [t06]", function(){})
})
context("4nested suite [s04]", function(){
it("test [t07]", function(){})
it("test [t08]", function(){})
context("5nested nested suite [s05]", function(){
it("test [t09]", function(){})
it("test [t10]", function(){})
})
})
})
})
describe("6suite [s06]", function(){
// beforeEach(function(){
// console.log("suite beforeEach")
// })
it("test [t11]", function(){})
})
</script>
</head>
<body></body>
</html>
@@ -1,24 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Test .only Fixture</title>
<script type="text/javascript">
window.mocha = new parent.Mocha({
reporter: function(){}
})
</script>
<script type="text/javascript" src="/lib/public/js/iframe.js"></script>
<script type="text/javascript">
it("foos [123]", function(){})
it("bars [456]", function(){})
describe("nested [AbC]", function(){
it.only("only [656]", function(){})
})
</script>
</head>
<body></body>
</html>
@@ -1,3 +1,5 @@
{ $, _ } = window.testUtils
describe "$Cypress.Cy Checkbox Commands", ->
enterCommandTestingMode()
@@ -1,3 +1,5 @@
{ $, _, Promise } = window.testUtils
describe "$Cypress.Cy Clicking Commands", ->
enterCommandTestingMode()
@@ -1,3 +1,5 @@
{ $, _ } = window.testUtils
describe "$Cypress.Cy Focus Commands", ->
enterCommandTestingMode()
@@ -325,9 +327,9 @@ describe "$Cypress.Cy Focus Commands", ->
expect($ce.get(0)).to.eq ce.get(0)
it "can blur input[type=time]", (done) ->
@cy.$$("#input-types [type=time]").blur -> done()
@cy.$$("#time-without-value").blur -> done()
@cy.get("#input-types [type=time]").focus().invoke("val", "03:15:00").blur()
@cy.get("#time-without-value").focus().invoke("val", "03:15:00").blur()
it "delays 50ms before resolving", (done) ->
waited = false
@@ -1,3 +1,5 @@
{ $, _ } = window.testUtils
describe "$Cypress.Cy Form Commands", ->
enterCommandTestingMode()
@@ -1,3 +1,5 @@
{ $, _ } = window.testUtils
describe "$Cypress.Cy Miscellaneous Commands", ->
enterCommandTestingMode()
@@ -536,14 +538,13 @@ describe "$Cypress.Cy Miscellaneous Commands", ->
consoleProps = @log.attributes.consoleProps()
coords = @cy.getCoordinates($button)
logCoords = @log.get("coords")
eventOptions = consoleProps["Event options"]
expect(logCoords.x).to.be.closeTo(coords.x, 1) ## ensure we are within 1
expect(logCoords.y).to.be.closeTo(coords.y, 1) ## ensure we are within 1
expect(consoleProps.Command).to.eq "ttrigger"
expect(consoleProps["Event options"]).to.eql({
bubbles: true
cancelable: true
clientX: 168
clientY: 9
pageX: 168
pageY: 548
})
expect(eventOptions.bubbles).to.be.true
expect(eventOptions.cancelable).to.be.true
expect(eventOptions.clientX).to.be.be.a("number")
expect(eventOptions.clientY).to.be.be.a("number")
expect(eventOptions.pageX).to.be.be.a("number")
expect(eventOptions.pageY).to.be.be.a("number")
@@ -1,3 +1,5 @@
{ $, _ } = window.testUtils
describe "$Cypress.Cy Scrolling Commands", ->
enterCommandTestingMode("scrolling", {
container: { height: 200 }
@@ -1,3 +1,5 @@
{ $, _ } = window.testUtils
describe "$Cypress.Cy Select Commands", ->
enterCommandTestingMode()
@@ -1,3 +1,5 @@
{ $, _, Promise, bililiteRange } = window.testUtils
describe "$Cypress.Cy Text Commands", ->
enterCommandTestingMode()
@@ -403,95 +405,30 @@ describe "$Cypress.Cy Text Commands", ->
describe "value changing", ->
it "changes the elements value", ->
@cy.get(":text:first").type("a").then ($text) ->
@cy.get("#input-without-value").type("a").then ($text) ->
expect($text).to.have.value("a")
it "changes the elements value for multiple keys", ->
@cy.get(":text:first").type("foo").then ($text) ->
@cy.get("#input-without-value").type("foo").then ($text) ->
expect($text).to.have.value("foo")
it "can change input[type=number] values", ->
@cy.get("#input-types [type=number]").type("12").then ($text) ->
expect($text).to.have.value("12")
it "inserts text after existing text", ->
@cy.get("#input-with-value").type(" bar").then ($text) ->
expect($text).to.have.value("foo bar")
it "inserts text after existing text input by invoking val", ->
@cy.get(":text:first").invoke("val", "foo").type(" bar").then ($text) ->
@cy.get("#input-without-value").invoke("val", "foo").type(" bar").then ($text) ->
expect($text).to.have.value("foo bar")
it "inserts text after existing text on input[type=number]", ->
@cy.get("#number-with-value").type("34").then ($text) ->
expect($text).to.have.value("1234")
it "inserts text after existing text on input[type=number] by invoking val", ->
@cy.get("#input-types [type=number]").invoke("val", "12").type("34").then ($text) ->
expect($text).to.have.value("1234")
it "overwrites text when currently has selection", ->
## when the text is clicked we want to select everything in it
@cy.$$(":text:first").val("0").click ->
@cy.$$("#input-without-value").val("0").click ->
$(@).select()
@cy.get(":text:first").type("50").then ($input) ->
@cy.get("#input-without-value").type("50").then ($input) ->
expect($input).to.have.value("50")
it "overwrites text on input[type=number] when input has existing text selected", ->
## when the text is clicked we want to select everything in it
@cy.$$("#input-types [type=number]").val("0").click ->
$(@).select()
@cy.get("#input-types [type=number]").type("50").then ($input) ->
expect($input).to.have.value("50")
it "overwrites text on input[type=email] when input has existing text selected", ->
## when the text is clicked we want to select everything in it
@cy.$$("#input-types [type=email]").val("foo@bar.com").click ->
$(@).select()
@cy.get("#input-types [type=email]").type("bar@foo.com").then ($input) ->
expect($input).to.have.value("bar@foo.com")
it "can change input[type=email] values", ->
@cy.get("#input-types [type=email]").type("brian@foo.com").then ($text) ->
expect($text).to.have.value("brian@foo.com")
it "inserts text after existing text on input[type=email]", ->
@cy.get("#email-with-value").type("om").then ($text) ->
expect($text).to.have.value("brian@foo.com")
it "inserts text after existing text on input[type=email] by invoking val", ->
@cy.get("#input-types [type=email]").invoke("val", "brian@foo.c").type("om").then ($text) ->
expect($text).to.have.value("brian@foo.com")
it "can change input[type=password] values", ->
@cy.get("#input-types [type=password]").type("password").then ($text) ->
expect($text).to.have.value("password")
it "inserts text after existing text on input[type=password]", ->
@cy.get("#input-types [type=password]").invoke("val", "pass").type("word").then ($text) ->
expect($text).to.have.value("password")
it "can change [contenteditable] values", ->
@cy.get("#input-types [contenteditable]").type("foo").then ($div) ->
expect($div).to.have.text("foo")
it "inserts text after existing text on [contenteditable]", ->
@cy.get("#input-types [contenteditable]").invoke("text", "foo").type(" bar").then ($text) ->
expect($text).to.have.text("foo bar")
# it "can change input[type=date] values", ->
# @cy.get("#input-types [type=date").type("1986-03-14").then ($text) ->
# expect($text).to.have.value("1986-03-14")
# it "inserts text after existing text on input[type=date]", ->
# @cy.get("#input-types [type=date").invoke("val", "pass").type("word").then ($text) ->
# expect($text).to.have.value("date")
it "automatically moves the caret to the end if value is changed manually", ->
@cy.$$(":text:first").keypress (e) ->
@cy.$$("#input-without-value").keypress (e) ->
e.preventDefault()
key = String.fromCharCode(e.which)
@@ -502,11 +439,11 @@ describe "$Cypress.Cy Text Commands", ->
$input.val(val + key + "-")
@cy.get(":text:first").type("foo").then ($input) ->
@cy.get("#input-without-value").type("foo").then ($input) ->
expect($input).to.have.value("f-o-o-")
it "automatically moves the caret to the end if value is changed manually asynchronously", ->
@cy.$$(":text:first").keypress (e) ->
@cy.$$("#input-without-value").keypress (e) ->
key = String.fromCharCode(e.which)
$input = $(e.target)
@@ -515,64 +452,225 @@ describe "$Cypress.Cy Text Commands", ->
val = $input.val()
$input.val(val + "-")
@cy.get(":text:first").type("foo").then ($input) ->
@cy.get("#input-without-value").type("foo").then ($input) ->
expect($input).to.have.value("f-o-o-")
it "does not fire keypress when keydown is preventedDefault", (done) ->
@cy.$$(":text:first").get(0).addEventListener "keypress", (e) ->
@cy.$$("#input-without-value").get(0).addEventListener "keypress", (e) ->
done("should not have received keypress event")
@cy.$$(":text:first").get(0).addEventListener "keydown", (e) ->
@cy.$$("#input-without-value").get(0).addEventListener "keydown", (e) ->
e.preventDefault()
@cy.get(":text:first").type("foo").then -> done()
@cy.get("#input-without-value").type("foo").then -> done()
it "does not insert key when keydown is preventedDefault", ->
@cy.$$(":text:first").get(0).addEventListener "keydown", (e) ->
@cy.$$("#input-without-value").get(0).addEventListener "keydown", (e) ->
e.preventDefault()
@cy.get(":text:first").type("foo").then ($text) ->
@cy.get("#input-without-value").type("foo").then ($text) ->
expect($text).to.have.value("")
it "does not insert key when keypress is preventedDefault", ->
@cy.$$(":text:first").get(0).addEventListener "keypress", (e) ->
@cy.$$("#input-without-value").get(0).addEventListener "keypress", (e) ->
e.preventDefault()
@cy.get(":text:first").type("foo").then ($text) ->
@cy.get("#input-without-value").type("foo").then ($text) ->
expect($text).to.have.value("")
it "does not fire textInput when keypress is preventedDefault", (done) ->
@cy.$$(":text:first").get(0).addEventListener "textInput", (e) ->
@cy.$$("#input-without-value").get(0).addEventListener "textInput", (e) ->
done("should not have received textInput event")
@cy.$$(":text:first").get(0).addEventListener "keypress", (e) ->
@cy.$$("#input-without-value").get(0).addEventListener "keypress", (e) ->
e.preventDefault()
@cy.get(":text:first").type("foo").then -> done()
@cy.get("#input-without-value").type("foo").then -> done()
it "does not insert key when textInput is preventedDefault", ->
@cy.$$(":text:first").get(0).addEventListener "textInput", (e) ->
@cy.$$("#input-without-value").get(0).addEventListener "textInput", (e) ->
e.preventDefault()
@cy.get(":text:first").type("foo").then ($text) ->
@cy.get("#input-without-value").type("foo").then ($text) ->
expect($text).to.have.value("")
it "does not fire input when textInput is preventedDefault", (done) ->
@cy.$$(":text:first").get(0).addEventListener "input", (e) ->
@cy.$$("#input-without-value").get(0).addEventListener "input", (e) ->
done("should not have received input event")
@cy.$$(":text:first").get(0).addEventListener "textInput", (e) ->
@cy.$$("#input-without-value").get(0).addEventListener "textInput", (e) ->
e.preventDefault()
@cy.get(":text:first").type("foo").then -> done()
@cy.get("#input-without-value").type("foo").then -> done()
it "preventing default to input event should not affect anything", ->
@cy.$$(":text:first").get(0).addEventListener "input", (e) ->
@cy.$$("#input-without-value").get(0).addEventListener "input", (e) ->
e.preventDefault()
@cy.get(":text:first").type("foo").then ($input) ->
@cy.get("#input-without-value").type("foo").then ($input) ->
expect($input).to.have.value("foo")
describe "input[type=number]", ->
it "can change values", ->
@cy.get("#number-without-value").type("42").then ($text) ->
expect($text).to.have.value("42")
it "can input decimal", ->
@cy.get("#number-without-value").type("2.0").then ($input) ->
expect($input).to.have.value("2.0")
it "can utilize {selectall}", ->
@cy.get("#number-with-value").type("{selectall}99").then ($input) ->
expect($input).to.have.value("99")
it "can utilize arrows", ->
@cy.get("#number-with-value").type("{leftarrow}{leftarrow}{rightarrow}9").then ($input) ->
expect($input).to.have.value("192")
it "inserts text after existing text ", ->
@cy.get("#number-with-value").type("34").then ($text) ->
expect($text).to.have.value("1234")
it "inserts text after existing text input by invoking val", ->
@cy.get("#number-without-value").invoke("val", "12").type("34").then ($text) ->
expect($text).to.have.value("1234")
it "overwrites text on input[type=number] when input has existing text selected", ->
@cy.$$("#number-without-value").val("0").click ->
$(@).select()
@cy.get("#number-without-value").type("50").then ($input) ->
expect($input).to.have.value("50")
describe "input[type=email]", ->
it "can change values", ->
@cy.get("#email-without-value").type("brian@foo.com").then ($text) ->
expect($text).to.have.value("brian@foo.com")
it "can utilize {selectall}", ->
@cy.get("#email-with-value").type("{selectall}brian@foo.com").then ($text) ->
expect($text).to.have.value("brian@foo.com")
it "can utilize arrows", ->
@cy.get("#email-with-value").type("{leftarrow}{rightarrow}om").then ($text) ->
expect($text).to.have.value("brian@foo.com")
it "inserts text after existing text", ->
@cy.get("#email-with-value").type("om").then ($text) ->
expect($text).to.have.value("brian@foo.com")
it "inserts text after existing text input by invoking val", ->
@cy.get("#email-without-value").invoke("val", "brian@foo.c").type("om").then ($text) ->
expect($text).to.have.value("brian@foo.com")
it "overwrites text when input has existing text selected", ->
@cy.$$("#email-without-value").val("foo@bar.com").click ->
$(@).select()
@cy.get("#email-without-value").type("bar@foo.com").then ($input) ->
expect($input).to.have.value("bar@foo.com")
describe "input[type=password]", ->
it "can change values", ->
@cy.get("#password-without-value").type("password").then ($text) ->
expect($text).to.have.value("password")
it "inserts text after existing text", ->
@cy.get("#password-with-value").type("word").then ($text) ->
expect($text).to.have.value("password")
it "inserts text after existing text input by invoking val", ->
@cy.get("#password-without-value").invoke("val", "secr").type("et").then ($text) ->
expect($text).to.have.value("secret")
it "overwrites text when input has existing text selected", ->
@cy.$$("#password-without-value").val("secret").click ->
$(@).select()
@cy.get("#password-without-value").type("agent").then ($input) ->
expect($input).to.have.value("agent")
describe "input[type=date]", ->
it "can change values", ->
@cy.get("#date-without-value").type("1959-09-13").then ($text) ->
expect($text).to.have.value("1959-09-13")
it "overwrites existing value", ->
@cy.get("#date-with-value").type("1959-09-13").then ($text) ->
expect($text).to.have.value("1959-09-13")
it "overwrites existing value input by invoking val", ->
@cy.get("#date-without-value").invoke("val", "2016-01-01").type("1959-09-13").then ($text) ->
expect($text).to.have.value("1959-09-13")
describe "input[type=month]", ->
it "can change values", ->
@cy.get("#month-without-value").type("1959-09").then ($text) ->
expect($text).to.have.value("1959-09")
it "overwrites existing value", ->
@cy.get("#month-with-value").type("1959-09").then ($text) ->
expect($text).to.have.value("1959-09")
it "overwrites existing value input by invoking val", ->
@cy.get("#month-without-value").invoke("val", "2016-01").type("1959-09").then ($text) ->
expect($text).to.have.value("1959-09")
describe "input[type=week]", ->
it "can change values", ->
@cy.get("#week-without-value").type("1959-W09").then ($text) ->
expect($text).to.have.value("1959-W09")
it "overwrites existing value", ->
@cy.get("#week-with-value").type("1959-W09").then ($text) ->
expect($text).to.have.value("1959-W09")
it "overwrites existing value input by invoking val", ->
@cy.get("#week-without-value").invoke("val", "2016-W01").type("1959-W09").then ($text) ->
expect($text).to.have.value("1959-W09")
describe "input[type=time]", ->
it "can change values", ->
@cy.get("#time-without-value").type("01:23:45").then ($text) ->
expect($text).to.have.value("01:23:45")
it "overwrites existing value", ->
@cy.get("#time-with-value").type("12:34:56").then ($text) ->
expect($text).to.have.value("12:34:56")
it "overwrites existing value input by invoking val", ->
@cy.get("#time-without-value").invoke("val", "01:23:45").type("12:34:56").then ($text) ->
expect($text).to.have.value("12:34:56")
it "can be formatted HH:mm", ->
@cy.get("#time-without-value").type("01:23").then ($text) ->
expect($text).to.have.value("01:23")
it "can be formatted HH:mm:ss", ->
@cy.get("#time-without-value").type("01:23:45").then ($text) ->
expect($text).to.have.value("01:23:45")
it "can be formatted HH:mm:ss.S", ->
@cy.get("#time-without-value").type("01:23:45.9").then ($text) ->
expect($text).to.have.value("01:23:45.9")
it "can be formatted HH:mm:ss.SS", ->
@cy.get("#time-without-value").type("01:23:45.99").then ($text) ->
expect($text).to.have.value("01:23:45.99")
it "can be formatted HH:mm:ss.SSS", ->
@cy.get("#time-without-value").type("01:23:45.999").then ($text) ->
expect($text).to.have.value("01:23:45.999")
describe "[contenteditable]", ->
it "can change values", ->
@cy.get("#input-types [contenteditable]").type("foo").then ($div) ->
expect($div).to.have.text("foo")
it "inserts text after existing text", ->
@cy.get("#input-types [contenteditable]").invoke("text", "foo").type(" bar").then ($text) ->
expect($text).to.have.text("foo bar")
describe "specialChars", ->
context "{{}", ->
it "sets which and keyCode to 219", (done) ->
@@ -2043,6 +2141,192 @@ describe "$Cypress.Cy Text Commands", ->
@cy.$$("#animation-container").append(input)
context "[type=date]", ->
it "throws when chars is not a string", (done) ->
logs = []
@Cypress.on "log", (attrs, log) ->
logs.push(log)
@cy.on "fail", (err) =>
expect(logs.length).to.eq(2)
expect(err.message).to.eq("Typing into a date input with cy.type() requires a valid date with the format 'yyyy-MM-dd'. You passed: 1989")
done()
@cy.get("#date-without-value").type(1989)
it "throws when chars is invalid format", (done) ->
logs = []
@Cypress.on "log", (attrs, log) ->
logs.push(log)
@cy.on "fail", (err) =>
expect(logs.length).to.eq(2)
expect(err.message).to.eq("Typing into a date input with cy.type() requires a valid date with the format 'yyyy-MM-dd'. You passed: 01-01-1989")
done()
@cy.get("#date-without-value").type("01-01-1989")
it "throws when chars is invalid date", (done) ->
logs = []
@Cypress.on "log", (attrs, log) ->
logs.push(log)
@cy.on "fail", (err) =>
expect(logs.length).to.eq(2)
expect(err.message).to.eq("Typing into a date input with cy.type() requires a valid date with the format 'yyyy-MM-dd'. You passed: 1989-04-31")
done()
@cy.get("#date-without-value").type("1989-04-31")
context "[type=month]", ->
it "throws when chars is not a string", (done) ->
logs = []
@Cypress.on "log", (attrs, log) ->
logs.push(log)
@cy.on "fail", (err) =>
expect(logs.length).to.eq(2)
expect(err.message).to.eq("Typing into a month input with cy.type() requires a valid month with the format 'yyyy-MM'. You passed: 6")
done()
@cy.get("#month-without-value").type(6)
it "throws when chars is invalid format", (done) ->
logs = []
@Cypress.on "log", (attrs, log) ->
logs.push(log)
@cy.on "fail", (err) =>
expect(logs.length).to.eq(2)
expect(err.message).to.eq("Typing into a month input with cy.type() requires a valid month with the format 'yyyy-MM'. You passed: 01/2000")
done()
@cy.get("#month-without-value").type("01/2000")
it "throws when chars is invalid month", (done) ->
logs = []
@Cypress.on "log", (attrs, log) ->
logs.push(log)
@cy.on "fail", (err) =>
expect(logs.length).to.eq(2)
expect(err.message).to.eq("Typing into a month input with cy.type() requires a valid month with the format 'yyyy-MM'. You passed: 1989-13")
done()
@cy.get("#month-without-value").type("1989-13")
context "[type=week]", ->
it "throws when chars is not a string", (done) ->
logs = []
@Cypress.on "log", (attrs, log) ->
logs.push(log)
@cy.on "fail", (err) =>
expect(logs.length).to.eq(2)
expect(err.message).to.eq("Typing into a week input with cy.type() requires a valid week with the format 'yyyy-Www', where W is the literal character 'W' and ww is the week number (00-53). You passed: 23")
done()
@cy.get("#week-without-value").type(23)
it "throws when chars is invalid format", (done) ->
logs = []
@Cypress.on "log", (attrs, log) ->
logs.push(log)
@cy.on "fail", (err) =>
expect(logs.length).to.eq(2)
expect(err.message).to.eq("Typing into a week input with cy.type() requires a valid week with the format 'yyyy-Www', where W is the literal character 'W' and ww is the week number (00-53). You passed: 2005/W18")
done()
@cy.get("#week-without-value").type("2005/W18")
it "throws when chars is invalid week", (done) ->
logs = []
@Cypress.on "log", (attrs, log) ->
logs.push(log)
@cy.on "fail", (err) =>
expect(logs.length).to.eq(2)
expect(err.message).to.eq("Typing into a week input with cy.type() requires a valid week with the format 'yyyy-Www', where W is the literal character 'W' and ww is the week number (00-53). You passed: 1995-W60")
done()
@cy.get("#week-without-value").type("1995-W60")
context "[type=time]", ->
it "throws when chars is not a string", (done) ->
logs = []
@Cypress.on "log", (attrs, log) ->
logs.push(log)
@cy.on "fail", (err) =>
expect(logs.length).to.equal(2)
expect(err.message).to.equal("Typing into a time input with cy.type() requires a valid time with the format 'HH:mm', 'HH:mm:ss' or 'HH:mm:ss.SSS', where HH is 00-23, mm is 00-59, ss is 00-59, and SSS is 000-999. You passed: 9999")
done()
@cy.get("#time-without-value").type(9999)
it "throws when chars is invalid format (1:30)", (done) ->
logs = []
@Cypress.on "log", (attrs, log) ->
logs.push(log)
@cy.on "fail", (err) =>
expect(logs.length).to.equal(2)
expect(err.message).to.equal("Typing into a time input with cy.type() requires a valid time with the format 'HH:mm', 'HH:mm:ss' or 'HH:mm:ss.SSS', where HH is 00-23, mm is 00-59, ss is 00-59, and SSS is 000-999. You passed: 1:30")
done()
@cy.get("#time-without-value").type("1:30")
it "throws when chars is invalid format (01:30pm)", (done) ->
logs = []
@Cypress.on "log", (attrs, log) ->
logs.push(log)
@cy.on "fail", (err) =>
expect(logs.length).to.equal(2)
expect(err.message).to.equal("Typing into a time input with cy.type() requires a valid time with the format 'HH:mm', 'HH:mm:ss' or 'HH:mm:ss.SSS', where HH is 00-23, mm is 00-59, ss is 00-59, and SSS is 000-999. You passed: 01:30pm")
done()
@cy.get("#time-without-value").type("01:30pm")
it "throws when chars is invalid format (01:30:30.3333)", (done) ->
logs = []
@Cypress.on "log", (attrs, log) ->
logs.push(log)
@cy.on "fail", (err) =>
expect(logs.length).to.equal(2)
expect(err.message).to.equal("Typing into a time input with cy.type() requires a valid time with the format 'HH:mm', 'HH:mm:ss' or 'HH:mm:ss.SSS', where HH is 00-23, mm is 00-59, ss is 00-59, and SSS is 000-999. You passed: 01:30:30.3333")
done()
@cy.get("#time-without-value").type("01:30:30.3333")
it "throws when chars is invalid time", (done) ->
logs = []
@Cypress.on "log", (attrs, log) ->
logs.push(log)
@cy.on "fail", (err) =>
expect(logs.length).to.equal(2)
expect(err.message).to.equal("Typing into a time input with cy.type() requires a valid time with the format 'HH:mm', 'HH:mm:ss' or 'HH:mm:ss.SSS', where HH is 00-23, mm is 00-59, ss is 00-59, and SSS is 000-999. You passed: 01:60")
done()
@cy.get("#time-without-value").type("01:60")
context "#clear", ->
it "does not change the subject", ->
textarea = @cy.$$("textarea")
@@ -2096,6 +2380,10 @@ describe "$Cypress.Cy Text Commands", ->
@cy.get("#input-covered-in-span").clear({timeout: 1000, interval: 60})
it "works on input[type=number]", ->
@cy.get("#number-with-value").clear().then ($input) ->
expect($input.val()).to.equal("")
describe "assertion verification", ->
beforeEach ->
@allowErrors()
@@ -1,3 +1,5 @@
{ _ } = window.testUtils
describe "$Cypress.Cy Agents Commands", ->
enterCommandTestingMode()
@@ -1,3 +1,5 @@
{ $, _ } = window.testUtils
describe "$Cypress.Cy Aliasing Commands", ->
enterCommandTestingMode()
@@ -1,3 +1,5 @@
{ $, _ } = window.testUtils
describe "$Cypress.Cy Angular Commands", ->
enterCommandTestingMode()
@@ -1,3 +1,5 @@
{ $, _ } = window.testUtils
describe "$Cypress.Cy Assertion Commands", ->
enterCommandTestingMode()
@@ -1,3 +1,5 @@
{ _ } = window.testUtils
describe "$Cypress.Cy Commands", ->
enterCommandTestingMode()
@@ -1,3 +1,5 @@
{ $, _, Promise } = window.testUtils
describe "$Cypress.Cy Connectors Commands", ->
enterCommandTestingMode()
@@ -260,7 +262,7 @@ describe "$Cypress.Cy Connectors Commands", ->
return $input
.then ($input) ->
expectOriginal(@cy.state("subject")).not.to.be.instanceof @remoteWindow.$
expectOriginal(@cy.state("subject")).to.be.instanceof window.$
expectOriginal(@cy.state("subject")).to.be.instanceof $
it "does not nuke selector properties", ->
@cy
@@ -1,3 +1,5 @@
{ _, Promise } = window.testUtils
describe "$Cypress.Cy Cookie Commands", ->
enterCommandTestingMode()
@@ -1,3 +1,5 @@
{ _, Promise } = window.testUtils
describe "$Cypress.Cy Exec Command", ->
enterCommandTestingMode()
@@ -1,3 +1,5 @@
{ _ } = window.testUtils
describe "$Cypress.Cy Files Commands", ->
enterCommandTestingMode()
@@ -1,3 +1,5 @@
{ _ } = window.testUtils
describe "$Cypress.Cy Fixtures Commands", ->
enterCommandTestingMode()
@@ -1,3 +1,5 @@
{ _ } = window.testUtils
describe "$Cypress.Cy Location Commands", ->
enterCommandTestingMode()
@@ -1,3 +1,5 @@
{ $, _ } = window.testUtils
describe "$Cypress.Cy Misc Commands", ->
enterCommandTestingMode()
@@ -1,3 +1,5 @@
{ $, _, Promise, Cookies } = window.testUtils
describe "$Cypress.Cy Navigation Commands", ->
enterCommandTestingMode()
@@ -872,7 +874,9 @@ describe "$Cypress.Cy Navigation Commands", ->
@cy.visit("/foo.html")
it "displays loading_file_failed redirects when _resolveUrl resp is not ok", (done) ->
## FIXME: the following 5 tests hang when running all tests in this file
it.skip "displays loading_file_failed redirects when _resolveUrl resp is not ok", (done) ->
obj = {
isOkStatusCode: false
isHtml: true
@@ -923,8 +927,6 @@ describe "$Cypress.Cy Navigation Commands", ->
@cy.visit("/bar")
## FIXME: the following 4 tests hang when running all tests in this file
it.skip "displays loading_http_failed when _resolveUrl resp is not ok", (done) ->
obj = {
isOkStatusCode: false
@@ -1,3 +1,5 @@
{ $, _ } = window.testUtils
describe "$Cypress.Cy Querying Commands", ->
enterCommandTestingMode()
@@ -1,3 +1,5 @@
{ _, Promise } = window.testUtils
describe "$Cypress.Cy Request Commands", ->
enterCommandTestingMode()
@@ -686,7 +688,8 @@ describe "$Cypress.Cy Request Commands", ->
form: {foo: "bar"}
})
it "throws when status code doesnt start with 2 and failOnStatusCode is true", (done) ->
## FIXME: hangs for some reason
it.skip "throws when status code doesnt start with 2 and failOnStatusCode is true", (done) ->
@respondWith({
isOkStatusCode: false
status: 500
@@ -760,7 +763,8 @@ describe "$Cypress.Cy Request Commands", ->
}
})
it "does not include redirects when there were no redirects", (done) ->
## FIXME: hangs for some reason
it.skip "does not include redirects when there were no redirects", (done) ->
@respondWith({
isOkStatusCode: false
status: 500
@@ -844,7 +848,8 @@ describe "$Cypress.Cy Request Commands", ->
@cy.request("http://localhost:1234/foo")
context "displays error", ->
## FIXME: these hang for some reason
context.skip "displays error", ->
beforeEach ->
@respondWith({__error: "request failed"})
@@ -883,28 +888,28 @@ describe "$Cypress.Cy Request Commands", ->
@cy.request("http://localhost:1234/foo")
it "throws after timing out", (done) ->
@respondWith({isOkStatusCode: true, status: 200}, 250)
it "throws after timing out", (done) ->
@respondWith({isOkStatusCode: true, status: 200}, 250)
logs = []
logs = []
@Cypress.on "log", (attrs, @log) =>
logs.push(@log)
@Cypress.on "log", (attrs, @log) =>
logs.push(@log)
@cy.on "fail", (err) =>
expect(logs.length).to.eq(1)
expect(@log.get("error")).to.eq(err)
expect(@log.get("state")).to.eq("failed")
expect(err.message).to.eq("""
cy.request() timed out waiting 50ms for a response from your server.
@cy.on "fail", (err) =>
expect(logs.length).to.eq(1)
expect(@log.get("error")).to.eq(err)
expect(@log.get("state")).to.eq("failed")
expect(err.message).to.eq("""
cy.request() timed out waiting 50ms for a response from your server.
The request we sent was:
The request we sent was:
Method: GET
URL: http://localhost:1234/foo
Method: GET
URL: http://localhost:1234/foo
No response was received within the timeout.
""")
done()
No response was received within the timeout.
""")
done()
@cy.request({url: "http://localhost:1234/foo", timeout: 50})
@cy.request({url: "http://localhost:1234/foo", timeout: 50})
@@ -1,3 +1,5 @@
{ _, Promise } = window.testUtils
describe "$Cypress.Cy Screenshot Commands", ->
enterCommandTestingMode()
@@ -1,3 +1,5 @@
{ $, _ } = window.testUtils
describe "$Cypress.Cy Traversal Commands", ->
enterCommandTestingMode()
@@ -1,3 +1,5 @@
{ _, Promise } = window.testUtils
describe "$Cypress.Cy Waiting Commands", ->
enterCommandTestingMode()
@@ -1,3 +1,5 @@
{ $, _ } = window.testUtils
describe "$Cypress.Cy Window Commands", ->
enterCommandTestingMode()
@@ -1,3 +1,5 @@
{ _, Promise } = window.testUtils
# describe "$Cypress.Cy XHR Commands", ->
# enterCommandTestingMode()
@@ -1,3 +1,5 @@
{ $ } = window.testUtils
describe "$Cypress.Cy Coordinates Extensions", ->
enterCommandTestingMode()
@@ -1,3 +1,5 @@
{ $, _ } = window.testUtils
describe "$Cypress.Cy Ensure Extensions", ->
enterCommandTestingMode()
@@ -1,3 +1,5 @@
{ $, _ } = window.testUtils
describe "$Cypress.Cy Errors Extensions", ->
before ->
@iframe = $("<iframe />").appendTo $("body")
@@ -1,3 +1,5 @@
{ $ } = window.testUtils
describe "$Cypress.Cy Listeners Extensions", ->
context "iframe load", ->
before ->
@@ -1,3 +1,5 @@
{ $ } = window.testUtils
describe "$Cypress.Cy Snapshot Extension", ->
enterCommandTestingMode()
@@ -1,3 +1,5 @@
{ _ } = window.testUtils
describe "$Cypress Url:Changed Events", ->
enterCommandTestingMode()
@@ -1,3 +1,5 @@
{ _ } = window.testUtils
describe "$Cypress.Chai API", ->
beforeEach ->
@Cypress = $Cypress.create()
@@ -38,4 +40,4 @@ describe "$Cypress.Chai API", ->
it "null outs Cypress.chai", ->
expect(@Cypress.chai).to.be.ok
@Cypress.trigger("stop")
expect(@Cypress.chai).to.be.null
expect(@Cypress.chai).to.be.null
@@ -1,3 +1,5 @@
{ _ } = window.testUtils
## FIXME: Needs re-write to test new interface
describe.skip "$Cypress.Commands API", ->
beforeEach ->
@@ -1,3 +1,5 @@
{ _, Cookies } = window.testUtils
describe "$Cypress.Cookies API", ->
beforeEach ->
@Cypress = $Cypress.create()
@@ -1,3 +1,5 @@
{ $, _, Promise, moment } = window.testUtils
describe "$Cypress.Cy API", ->
context "unit", ->
before ->
@@ -1,3 +1,5 @@
{ _ } = window.testUtils
describe "$Cypress API", ->
beforeEach ->
@Cypress = $Cypress.create()
@@ -1,3 +1,5 @@
{ $ } = window.testUtils
describe "$Cypress.jQuery Extensions", ->
enterCommandTestingMode()
@@ -1,3 +1,5 @@
{ _ } = window.testUtils
describe "$Cypress.LocalStorage API", ->
before ->
@clear = (remote) =>
@@ -1,3 +1,5 @@
{ $, _, Promise } = window.testUtils
describe "$Cypress.Log API", ->
describe "instances", ->
@@ -1,3 +1,5 @@
{ $, _ } = window.testUtils
m = window.mocha
describe "$Cypress.Mocha API", ->
@@ -1,3 +1,5 @@
{ _ } = window.testUtils
describe "$Cypress.Runner API", ->
beforeEach ->
@Cypress = $Cypress.create()
@@ -1,3 +1,5 @@
{ $, _ } = window.testUtils
describe "$Cypress.Cy Server API", ->
beforeEach ->
@iframe = $("<iframe />").appendTo $("body")
@@ -1,3 +1,5 @@
{ $ } = window.testUtils
describe "$Cypress.utils API", ->
enterCommandTestingMode()
+14 -3
View File
@@ -471,7 +471,15 @@ InputRange.prototype._nativeRange = function(bounds) {
return bounds || [0, this.length()];
};
InputRange.prototype._nativeSelect = function (rng){
this._el.setSelectionRange(rng[0], rng[1]);
try {
this._el.setSelectionRange(rng[0], rng[1]);
} catch (e) {
if (typeof this._el.select === 'function') {
this._el.select()
} else {
console.error('Failed to select text on', this._el)
}
}
};
InputRange.prototype._nativeSelection = function(){
var originalType = this._el.type
@@ -490,20 +498,23 @@ InputRange.prototype._nativeSelection = function(){
var start = this._el.selectionStart
var end = this._el.selectionEnd
var selection = [start, end]
//// HACK:
//// selection start and end don't report correctly when input
//// already has a value set, so if there's a value and there is no
//// native selection, force it to be at the end of the text
if (this._el.value && !start && !end) {
var length = this._el.value.length
return [length, length]
selection = [length, length]
}
if (shouldChangeType) {
this._el.type = originalType
this._el.focus()
}
return [start, end]
return selection
};
InputRange.prototype._nativeGetText = function(rng){
return this._el.value.substring(rng[0], rng[1]);
+1 -1
View File
@@ -4,7 +4,7 @@
"description": "Interal Cypress repo for managing https://example.cypress.io",
"main": "index.js",
"scripts": {
"postinstall": "npm run build",
"postinstall": "echo 'This project needs: npm run build'",
"clean-deps": "rm -rf node_modules",
"test": "NODE_ENV=test mocha",
"test-e2e": "cypress run",
+1 -1
View File
@@ -4,7 +4,7 @@
"description": "Cypress Chrome Extension",
"main": "index.js",
"scripts": {
"postinstall": "npm run build",
"postinstall": "echo 'This project needs: npm run build'",
"watch": "gulp watch",
"build": "gulp build",
"build-prod": "gulp build",
+5 -1
View File
@@ -10,5 +10,9 @@ if (!module.parent) {
// quick way to check if TS is working
console.log('Launcher project exports')
console.log(launcher)
console.log('please use it as a module, not from CLI')
console.log('⛔️ please use it as a module, not from CLI')
launcher.detect().then(browsers => {
console.log('detected %d browser(s)', browsers.length)
console.log(browsers)
}, console.error)
}
+45 -9
View File
@@ -1,14 +1,9 @@
import {log} from './log'
import {find, map} from 'lodash'
import cp = require('child_process')
import {BrowserNotFoundError} from './types'
import {Browser, FoundBrowser, BrowserNotFoundError} from './types'
type FoundBrowser = {
name: string,
path?: string
}
const browserNotFoundErr = (browsers:FoundBrowser[], name: string): BrowserNotFoundError => {
const browserNotFoundErr = (browsers: FoundBrowser[], name: string): BrowserNotFoundError => {
const available = map(browsers, 'name').join(', ')
const err: BrowserNotFoundError
@@ -17,9 +12,49 @@ const browserNotFoundErr = (browsers:FoundBrowser[], name: string): BrowserNotFo
return err
}
const googleChromeStable: Browser = {
name: 'Google Chrome Stable',
versionRegex: /Google Chrome (\S+)/,
profile: true,
binary: 'google-chrome-stable'
}
const googleChromeAlias: Browser = {
name: 'Google Chrome',
versionRegex: /Google Chrome (\S+)/,
profile: true,
binary: 'chrome'
}
/** list of all browsers we can detect and use */
export const browsers: Browser[] = [
{
name: 'chrome',
displayName: 'Chrome',
versionRegex: /Google Chrome (\S+)/,
profile: true,
binary: 'google-chrome'
},{
name: 'chromium',
displayName: 'Chromium',
versionRegex: /Chromium (\S+)/,
profile: true,
binary: 'chromium-browser'
},{
name: 'canary',
displayName: 'Canary',
versionRegex: /Google Chrome Canary (\S+)/,
profile: true,
binary: 'google-chrome-canary'
},
// a couple of fallbacks
googleChromeStable,
googleChromeAlias
]
/** starts a browser by name and opens URL if given one */
export function launch (browsers:FoundBrowser[],
name:string, url?:string, args:string[] = []) {
export function launch (browsers: FoundBrowser[],
name: string, url?: string, args: string[] = []) {
log('launching browser %s to open %s', name, url)
const browser = find(browsers, {name})
@@ -35,5 +70,6 @@ export function launch (browsers:FoundBrowser[],
args = [url].concat(args)
}
log('spawning browser %s with args %s', browser.path, args.join(' '))
return cp.spawn(browser.path, args, {stdio: 'ignore'})
}
-22
View File
@@ -1,22 +0,0 @@
import {parse, find} from './util'
import path = require('path')
import Promise = require('bluebird')
const canary = {
version: (p: string) =>
parse(p, 'KSVersion'),
path: () => find('com.google.Chrome.canary'),
get (executable: string) {
return this.path()
.then (p => {
return Promise.props({
path: path.join(p, executable),
version: this.version(p)
})
})
}
}
export default canary
-28
View File
@@ -1,28 +0,0 @@
import {log} from '../log'
import {parse, find} from './util'
import path = require('path')
import Promise = require('bluebird')
const chrome = {
version (p: string) {
return parse(p, 'KSVersion')
},
path () {
return find('com.google.Chrome')
},
get (executable: string) {
log('Looking for Chrome %s', executable)
return this.path()
.then(p => {
return Promise.props({
path: path.join(p, executable),
version: this.version(p)
})
})
}
}
export default chrome
-25
View File
@@ -1,25 +0,0 @@
import {find, parse} from './util'
import path = require('path')
import Promise = require('bluebird')
const chromium = {
version (p: string) {
return parse(p, 'CFBundleShortVersionString')
},
path () {
return find('org.chromium.Chromium')
},
get (executable: string) {
return this.path()
.then(p =>
Promise.props({
path: path.join(p, executable),
version: this.version(p)
})
)
}
}
export default chromium
+37 -8
View File
@@ -1,11 +1,40 @@
import canary from './canary'
import chrome from './chrome'
import chromium from './chromium'
import {findApp} from './util'
import {Browser} from '../types'
import {detectBrowserLinux} from '../linux'
import {log} from '../log'
import {merge, partial} from 'ramda'
const browsers = {
chrome,
canary,
chromium
const detectCanary = partial(findApp,
['Contents/MacOS/Google Chrome Canary', 'com.google.Chrome.canary', 'KSVersion'])
const detectChrome = partial(findApp,
['Contents/MacOS/Google Chrome', 'com.google.Chrome', 'KSVersion'])
const detectChromium = partial(findApp,
['Contents/MacOS/Chromium', 'org.chromium.Chromium', 'CFBundleShortVersionString'])
type Detectors = {
[index: string]: Function
}
export default browsers
const browsers: Detectors = {
chrome: detectChrome,
canary: detectCanary,
chromium: detectChromium
}
export function detectBrowserDarwin (browser: Browser) {
let fn = browsers[browser.name]
if (!fn) {
// ok, maybe it is custom alias?
log('detecting custom browser %s on darwin', browser.name)
return detectBrowserLinux(browser)
}
return fn()
.then(merge({name: browser.name}))
.catch(() => {
log('could not detect %s using traditional Mac methods', browser.name)
log('trying linux search')
return detectBrowserLinux(browser)
})
}
+75 -22
View File
@@ -1,38 +1,91 @@
import {log} from '../log'
import {NotInstalledError} from '../types'
import {prop, tap} from 'ramda'
import execa = require('execa')
import fs = require('fs-extra')
import path = require('path')
import plist = require('plist')
export function parse (p: string, prop: string) {
/** parses Info.plist file from given application and returns a property */
export function parse (p: string, property: string): Promise<string> {
const pl = path.join(p, 'Contents', 'Info.plist')
log('reading property file "%s"', pl)
const failed = (e: Error) => {
const msg = `Info.plist not found: ${pl}
${e.message}`
const err = new Error(msg) as NotInstalledError
err.notInstalled = true
log('could not read Info.plist for %s', pl)
throw err
}
return fs.readFile(pl, 'utf8')
.then(str => plist.parse(str))
.then(x => x[prop])
.catch((e) => {
const msg = `Info.plist not found: ${pl}
${e.message}`
const err = new Error(msg) as NotInstalledError
err.notInstalled = true
throw err
})
.then(plist.parse)
.then(prop(property))
.catch(failed)
}
export function find (id: string): Promise<string> {
/** uses mdfind to find app using Ma app id like 'com.google.Chrome.canary' */
export function mdfind (id: string): Promise<string> {
const cmd = `mdfind 'kMDItemCFBundleIdentifier=="${id}"' | head -1`
log('looking for bundle id %s using command: %s', id, cmd)
const logFound = (str: string) => {
log('found %s at %s', id, str)
return str
}
const failedToFind = () => {
log('could not find %s', id)
const err = new Error(`Browser not installed: ${id}`) as NotInstalledError
err.notInstalled = true
throw err
}
return execa.shell(cmd)
.then(result => result.stdout)
.then((str: string) => {
log('found %s at %s', id, str)
return str
})
.catch(() => {
log('could not find %s', id)
const err = new Error(`Browser not installed: ${id}`) as NotInstalledError
err.notInstalled = true
throw err
})
.then(prop('stdout'))
.then(tap(logFound))
.catch(failedToFind)
}
export type AppInfo = {
path: string,
version: string
}
function formApplicationPath (executable: string) {
const parts = executable.split('/')
const name = parts[parts.length - 1]
const appName = `${name}.app`
return path.join('/Applications', appName)
}
/** finds an application and its version */
export function findApp (executable: string, appId: string, versionProperty: string): Promise<AppInfo> {
log('looking for app %s id %s', executable, appId)
const findVersion = (foundPath: string) =>
parse(foundPath, versionProperty)
.then((version) => {
return {
path: path.join(foundPath, executable),
version
}
})
const tryMdFind = () => {
return mdfind(appId)
.then(findVersion)
}
const tryFullApplicationFind = () => {
const applicationPath = formApplicationPath(executable)
log('looking for application %s', applicationPath)
return findVersion(applicationPath)
}
return tryMdFind()
.catch(tryFullApplicationFind)
}
+46 -62
View File
@@ -1,36 +1,15 @@
import {linuxBrowser} from './linux'
import darwin from './darwin'
import {detectBrowserLinux} from './linux'
import {detectBrowserDarwin} from './darwin'
import {log} from './log'
import {Browser, NotInstalledError} from './types'
import {browsers} from './browsers'
import * as Bluebird from 'bluebird'
import {merge, pick, tap, uniqBy, prop} from 'ramda'
import _ = require('lodash')
import os = require('os')
// import Promise = require('bluebird')
const browsers:Browser[] = [
{
name: 'chrome',
re: /Google Chrome (\S+)/,
profile: true,
binary: 'google-chrome',
executable: 'Contents/MacOS/Google Chrome'
},{
name: 'chromium',
re: /Chromium (\S+)/,
profile: true,
binary: 'chromium-browser',
executable: 'Contents/MacOS/Chromium'
},{
name: 'canary',
re: /Google Chrome Canary (\S+)/,
profile: true,
binary: 'google-chrome-canary',
executable: 'Contents/MacOS/Google Chrome Canary'
}
]
const setMajorVersion = (obj:Browser) => {
const setMajorVersion = (obj: Browser) => {
if (obj.version) {
obj.majorVersion = obj.version.split('.')[0]
log('browser %s version %s major version %s',
@@ -39,51 +18,56 @@ const setMajorVersion = (obj:Browser) => {
return obj
}
type MacBrowserName = 'chrome' | 'chromium' | 'canary'
function lookup (platform:string, obj:Browser):Promise<Object> {
log('looking up %s on %s platform', obj.name, platform)
switch (platform) {
case 'darwin':
const browserName:MacBrowserName = obj.name as MacBrowserName
const fn = darwin[browserName]
if (fn) {
return fn.get(obj.executable) as any as Promise<Object>
}
const err: NotInstalledError =
new Error(`Browser not installed: ${obj.name}`) as NotInstalledError
err.notInstalled = true
throw err
case 'linux':
return linuxBrowser.get(obj.binary, obj.re) as any as Promise<Object>
default:
throw new Error(`Cannot lookup browser ${obj.name} on ${platform}`)
}
type BrowserDetector = (browser: Browser) => Promise<Object>
type Detectors = {
[index: string]: BrowserDetector
}
const detectors: Detectors = {
darwin: detectBrowserDarwin,
linux: detectBrowserLinux
}
function checkOneBrowser(browser:Browser) {
function lookup (platform: NodeJS.Platform, obj: Browser): Promise<Object> {
log('looking up %s on %s platform', obj.name, platform)
const detector = detectors[platform]
if (!detector) {
throw new Error(`Cannot lookup browser ${obj.name} on ${platform}`)
}
return detector(obj)
}
function checkOneBrowser (browser: Browser) {
const platform = os.platform()
const pickBrowserProps = pick(['name', 'displayName', 'type', 'version', 'path'])
const logBrowser = (props: any) => {
log('setting major version for %j', props)
}
const failed = (err: NotInstalledError) => {
if (err.notInstalled) {
log('browser %s not installed', browser.name)
return false
}
throw err
}
return lookup(platform, browser)
.then((props:object) => {
return _.chain({})
.extend(browser, props)
.pick('name', 'type', 'version', 'path')
.value()
})
.then(merge(browser))
.then(pickBrowserProps)
.then(tap(logBrowser))
.then(setMajorVersion)
.catch(err => {
if (err.notInstalled) {
log('browser %s not installed', browser.name)
return false
}
throw err
})
.catch(failed)
}
/** returns list of detected browsers */
function detectBrowsers (): Bluebird<Browser[]> {
return Bluebird.map(browsers, checkOneBrowser)
.then(_.compact) as Bluebird<Browser[]>
// we can detect same browser under different aliases
// tell them apart by the full version property
const removeDuplicates = uniqBy(prop('version'))
return Bluebird.mapSeries(browsers, checkOneBrowser)
.then(_.compact)
.then(removeDuplicates) as Bluebird<Browser[]>
}
export default detectBrowsers
+4 -4
View File
@@ -8,12 +8,12 @@ const Promise = require('bluebird')
const missingConfig = () =>
Promise.reject(new Error('You must provide a path to a config file.'))
const wrap = (all:Browser[]) => ({
launch: (name:string, url:string, args = []) =>
const wrap = (all: Browser[]) => ({
launch: (name: string, url: string, args = []) =>
launch(all, name, url, args)
})
const init = (browsers:Browser[]) =>
const init = (browsers: Browser[]) =>
browsers ? wrap(browsers) : detect().then(wrap)
const api: LauncherApi = init as any as LauncherApi
@@ -24,7 +24,7 @@ const update = (pathToConfig?: string) => {
}
// detect the browsers and set the config
const saveBrowsers = (browers:Browser[]) =>
const saveBrowsers = (browers: Browser[]) =>
writeJson(pathToConfig, browers, {spaces: 2})
return detect()
+32 -22
View File
@@ -1,30 +1,40 @@
import cp = require('child_process')
import Promise = require('bluebird')
import {NotInstalledError} from '../types'
const execAsync = Promise.promisify(cp.exec)
import {log} from '../log'
import {prop, trim} from 'ramda'
import {FoundBrowser, Browser, NotInstalledError} from '../types'
import execa = require('execa')
const notInstalledErr = (name: string) => {
const err: NotInstalledError = new Error(`Browser not installed: ${name}`) as NotInstalledError
const err: NotInstalledError =
new Error(`Browser not installed: ${name}`) as NotInstalledError
err.notInstalled = true
throw err
}
export const linuxBrowser = {
get: (binary: string, re: RegExp): Promise<any> => {
return execAsync(`${binary} --version`)
.call('trim')
.then (stdout => {
const m = re.exec(stdout)
if (m) {
return {
path: binary,
version: m[1]
}
} else {
return notInstalledErr(binary)
}
})
.catch(() => notInstalledErr(binary))
function getLinuxBrowser (name: string, binary: string, versionRegex: RegExp): Promise<FoundBrowser> {
const getVersion = (stdout: string) => {
const m = versionRegex.exec(stdout)
if (m) {
return m[1]
}
return notInstalledErr(binary)
}
const cmd = `${binary} --version`
log('looking using command "%s"', cmd)
return execa.shell(cmd)
.then(prop('stdout'))
.then(trim)
.then(getVersion)
.then((version) => {
return {
name,
version,
path: binary
}
})
.catch(() => notInstalledErr(binary))
}
export function detectBrowserLinux (browser: Browser) {
return getLinuxBrowser(browser.name, browser.binary, browser.versionRegex)
}
+16 -3
View File
@@ -1,14 +1,27 @@
/** TODO this are typical browser names, not just Mac */
export type MacBrowserName = 'chrome' | 'chromium' | 'canary' | string
export type PlatformName = 'darwin' | 'linux'
export type Browser = {
name: string,
re: RegExp,
/** short browser name */
name: MacBrowserName,
/** Optional display name */
displayName?: string,
/** RegExp to use to extract version from something like "Google Chrome 58.0.3029.110" */
versionRegex: RegExp,
profile: boolean,
binary: string,
executable: string,
version?: string,
majorVersion?: string,
page?: string
}
export type FoundBrowser = {
name: string,
path?: string
}
interface ExtraLauncherMethods {
update: Function,
detect: Function
+8 -5
View File
@@ -8,7 +8,8 @@
"pretest": "npm run lint",
"test": "mocha",
"clean-deps": "rm -rf node_modules",
"lint": "tslint --type-check --project .. --fix --format stylish launcher/lib/*.ts launcher/lib/**/*.ts"
"clean": "rm lib/*.js lib/**/*.js || true",
"lint": "tslint --type-check --project . --fix --format stylish lib/*.ts lib/**/*.ts"
},
"repository": {
"type": "git",
@@ -31,16 +32,18 @@
"mocha": "^2.4.5",
"sinon": "^1.17.3",
"sinon-chai": "^2.8.0",
"tslint": "^5.2.0",
"tslint-config-standard": "^5.0.2",
"typescript": "^2.3.2"
"tslint": "5.3.2",
"tslint-config-standard": "5.0.2",
"typescript": "2.3.2"
},
"dependencies": {
"@types/ramda": "0.0.10",
"bluebird": "^3.3.5",
"debug": "^2.6.6",
"execa": "^0.6.3",
"fs-extra": "^3.0.0",
"lodash": "^4.11.1",
"plist": "^1.2.0"
"plist": "^1.2.0",
"ramda": "^0.23.0"
}
}
+10
View File
@@ -0,0 +1,10 @@
{
"extends": "./../ts/tsconfig.json",
"include": [
"lib/*.ts",
"lib/**/*.ts"
],
"files": [
"./../ts/index.d.ts"
]
}
+1 -1
View File
@@ -4,7 +4,7 @@
"main": "lib/reporter",
"browser": "src/main",
"scripts": {
"postinstall": "npm run build",
"postinstall": "echo 'This project needs: npm run build'",
"build": "node ./scripts/build-dev.js",
"build-prod": "node ./scripts/build-prod.js",
"watch": "node ./scripts/watch.js",
+1 -1
View File
@@ -12,7 +12,7 @@
},
"homepage": "https://github.com/cypress-io/cypress-core-runner#readme",
"scripts": {
"postinstall": "npm run build",
"postinstall": "echo 'This project needs: npm run build'",
"build": "node ./scripts/build-dev.js",
"build-prod": "node ./scripts/build-prod.js",
"watch": "node ./scripts/watch.js",
+9
View File
@@ -0,0 +1,9 @@
# lightweight logging for Node
# only shows log messages if running with
# DEBUG=cypress:start ...
# or
# DEBUG=cypress:* ...
# use
# log = require('./log')
# log('working in %s', process.cwd())
module.exports = require('debug')('cypress:server')
+53 -52
View File
@@ -244,6 +244,35 @@ module.exports = {
openProject.launch(browser, spec, browserOpts)
listenForProjectEnd: (project) ->
new Promise (resolve) ->
## dont ever end if we're in 'gui' debugging mode
return if gui
onEarlyExit = (errMsg) ->
## probably should say we ended
## early too: (Ended Early: true)
## in the stats
obj = {
error: errors.stripAnsi(errMsg)
failures: 1
tests: 0
passes: 0
pending: 0
duration: 0
failingTests: []
}
resolve(obj)
onEnd = (obj) =>
resolve(obj)
## when our project fires its end event
## resolve the promise
project.once("end", onEnd)
project.once("exitEarlyWithErr", onEarlyExit)
waitForBrowserToConnect: (options = {}) ->
{ project, id, timeout } = options
@@ -295,65 +324,37 @@ module.exports = {
waitForTestsToFinishRunning: (options = {}) ->
{ project, gui, screenshots, started, end, name, cname, videoCompression } = options
new Promise (resolve, reject) =>
## dont ever end if we're in 'gui' debugging mode
return if gui
@listenForProjectEnd(project)
.then (obj) =>
finish = ->
project
.getConfig()
.then (cfg) ->
obj.config = cfg
.return(obj)
onFinish = (obj) =>
finish = ->
project
.getConfig()
.then (cfg) ->
obj.config = cfg
.finally ->
resolve(obj)
if end
obj.video = name
if end
obj.video = name
if screenshots
obj.screenshots = screenshots
if screenshots
obj.screenshots = screenshots
@displayStats(obj)
@displayStats(obj)
if screenshots and screenshots.length
@displayScreenshots(screenshots)
if screenshots and screenshots.length
@displayScreenshots(screenshots)
ft = obj.failingTests
ft = obj.failingTests
if ft and ft.length
obj.failingTests = Reporter.setVideoTimestamp(started, ft)
if ft and ft.length
obj.failingTests = Reporter.setVideoTimestamp(started, ft)
if end
@postProcessRecording(end, name, cname, videoCompression)
.then(finish)
## TODO: add a catch here
else
finish()
onEarlyExit = (errMsg) ->
## probably should say we ended
## early too: (Ended Early: true)
## in the stats
obj = {
error: errors.stripAnsi(errMsg)
failures: 1
tests: 0
passes: 0
pending: 0
duration: 0
failingTests: []
}
onFinish(obj)
onEnd = (obj) =>
onFinish(obj)
## when our project fires its end event
## resolve the promise
project.once("end", onEnd)
project.once("exitEarlyWithErr", onEarlyExit)
if end
@postProcessRecording(end, name, cname, videoCompression)
.then(finish)
## TODO: add a catch here
else
finish()
trashAssets: (options = {}) ->
if options.trashAssetsBeforeHeadlessRuns is true
+3 -6
View File
@@ -360,13 +360,10 @@ class Project extends EE
errors.throw("NO_PROJECT_FOUND_AT_PROJECT_ROOT", @projectRoot)
createCiProject: (projectDetails) ->
Promise.all([
user.ensureAuthToken()
@getConfig()
])
.spread (authToken, cfg) ->
user.ensureAuthToken()
.then (authToken) =>
git
.init(cfg.projectRoot)
.init(@projectRoot)
.getRemoteOrigin()
.then (remoteOrigin) ->
api.createProject(projectDetails, remoteOrigin, authToken)
+3
View File
@@ -1,5 +1,8 @@
FileUtil = require("./util/file")
appData = require("./util/app_data")
log = require('./log')
log('making saved state from %s', process.cwd())
module.exports = new FileUtil({
path: appData.path("state.json")
+1 -1
View File
@@ -51,7 +51,7 @@ module.exports =
@_err("ERROR_WRITING_FILE", file, err)
_write: (file, obj = {}) ->
fs.writeJsonAsync(file, obj, {spaces: 2})
fs.outputJsonAsync(file, obj, {spaces: 2})
.return(obj)
.catch (err) =>
@_logWriteErr(file, err)

Some files were not shown because too many files have changed in this diff Show More