From 45dff3119271470f9c74ee6044b9665f72da698a Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Fri, 22 Dec 2017 11:33:46 -0500 Subject: [PATCH 1/4] ts: document common assertions, cy and Cypress --- cli/types/index.d.ts | 141 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 129 insertions(+), 12 deletions(-) diff --git a/cli/types/index.d.ts b/cli/types/index.d.ts index fa6f50a2e1f9..a3f5a276d79e 100644 --- a/cli/types/index.d.ts +++ b/cli/types/index.d.ts @@ -30,7 +30,7 @@ declare namespace Cypress { type ViewportOrientation = "portrait" | "landscape" type PrevSubject = "optional" | "element" | "document" | "window" - interface CommandOptions { + interface CommandOptions { prevSubject: boolean | PrevSubject | PrevSubject[] } interface ObjectLike { @@ -104,6 +104,12 @@ declare namespace Cypress { Promise: Bluebird.BluebirdStatic /** * Cypress version string. i.e. "1.1.2" + * @see https://on.cypress.io/version + * @example + * expect(Cypress.version).to.be.a('string') + * if (Cypress.version === '1.2.0') { + * // test something specific + * } */ version: string @@ -403,15 +409,46 @@ declare namespace Cypress { * Get the DOM element that is currently focused. * * @see https://on.cypress.io/focused + * @example + * // Get the element that is focused + * cy.focused().then(function($el) { + * // do something with $el + * }) + * // Blur the element with focus + * cy.focused().blur() + * // Make an assertion on the focused element + * cy.focused().should('have.attr', 'name', 'username') */ focused(options?: Partial): Chainable /** - * Get one or more DOM elements by selector or alias. + * Get one or more DOM elements by node name: input, button, etc. * @see https://on.cypress.io/get + * @example + * cy.get('input').should('be.disabled') + * cy.get('button').should('be.visible') */ get(selector: K, options?: Partial): Chainable> + /** + * Get one or more DOM elements by selector. + * The querying behavior of this command matches exactly how $(…) works in jQuery. + * @see https://on.cypress.io/get + * @example + * cy.get('.list>li') // Yield the
  • 's in <.list> + * cy.get('ul li:first').should('have.class', 'active') + * cy.get('.dropdown-menu').click() + */ get(selector: string, options?: Partial): Chainable> + /** + * Get one or more DOM elements by alias. + * @see https://on.cypress.io/get#Alias + * @example + * // Get the aliased ‘todos’ elements + * cy.get('ul#todos').as('todos') + * //...hack hack hack... + * //later retrieve the todos + * cy.get('@todos') + */ get(alias: string, options?: Partial): Chainable /** @@ -757,10 +794,8 @@ declare namespace Cypress { * Type into a DOM element. * * @see https://on.cypress.io/type - * @example type - * ```ts - * cy.get('input').type('Hello, World') - * ``` + * @example + * cy.get('input').type('Hello, World') */ type(text: string, options?: Partial): Chainable @@ -795,10 +830,10 @@ declare namespace Cypress { * @param {string} url The URL to visit. If relative uses `baseUrl` * @param {VisitOptions} [options] Pass in an options object to change the default behavior of `cy.visit()` * @see https://on.cypress.io/visit - * @example visit - * ```ts - * cy.visit('http://localhost:3000') - * ``` + * @example + * cy.visit('http://localhost:3000') + * cy.visit('/somewhere') // opens ${baseUrl}/somewhere + * */ visit(url: string, options?: Partial): Chainable @@ -1235,6 +1270,12 @@ declare namespace Cypress { } // Kind of onerous, but has a nice auto-complete. Also fallbacks at the end for custom stuff + /** + * @see https://on.cypress.io/should + * + * @interface Chainer + * @template Subject + */ interface Chainer { // chai (chainer: 'be.a', value: string): Chainable @@ -1264,12 +1305,28 @@ declare namespace Cypress { (chainer: 'contain', value: any): Chainable (chainer: 'decrease', value: object, property: string): Chainable (chainer: 'deep.equal', value: Subject): Chainable + /** + * Check if current element exists in the DOM + * @see https://on.cypress.io/should + * @see https://on.cypress.io/assertions + * @example + * // retry until navigation is added to the DOM + * cy.get('nav').should('exist') + */ (chainer: 'exist'): Chainable (chainer: 'eq', value: any): Chainable (chainer: 'eql', value: any): Chainable (chainer: 'equal', value: any): Chainable (chainer: 'have.any.keys', ...value: any[]): Chainable (chainer: 'have.deep.property', value: string, match?: any): Chainable + /** + * Check if current subject has expected length + * @see https://on.cypress.io/should + * @see https://on.cypress.io/assertions + * @example + * // retry until we find 3 matching + * cy.get('li.selected').should('have.length', 3) + */ (chainer: 'have.length', value: number): Chainable (chainer: 'have.length.greaterThan', value: number): Chainable (chainer: 'have.length.gt', value: number): Chainable @@ -1321,6 +1378,14 @@ declare namespace Cypress { (chainer: 'not.contain', value: any): Chainable (chainer: 'not.decrease', value: object, property: string): Chainable (chainer: 'not.deep.equal', value: Subject): Chainable + /** + * Check if current element does not exists in the DOM + * @see https://on.cypress.io/should + * @see https://on.cypress.io/assertions + * @example + * // retry until loading spinner no longer exists + * cy.get('#loading').should('not.exist') + */ (chainer: 'not.exist'): Chainable (chainer: 'not.eq', value: any): Chainable (chainer: 'not.eql', value: any): Chainable @@ -1389,16 +1454,38 @@ declare namespace Cypress { (chainer: 'not.returned', value: any): Chainable // jquery-chai + /** + * Check if state of an element + * @see https://on.cypress.io/should + * @see https://on.cypress.io/assertions + * @example + * // retry until our radio is checked + * cy.get(':radio').should('be.checked') + */ (chainer: 'be.checked'): Chainable (chainer: 'be.disabled'): Chainable (chainer: 'be.empty'): Chainable (chainer: 'be.enabled'): Chainable (chainer: 'be.hidden'): Chainable (chainer: 'be.selected'): Chainable + /** + * Check if current subject is visible + * @see https://on.cypress.io/should + * @see https://on.cypress.io/assertions + * @example + * cy.get('#result').should('be.visible') + */ (chainer: 'be.visible'): Chainable (chainer: 'contain', value: string): Chainable (chainer: 'exist'): Chainable (chainer: 'have.attr', value: string, match?: string): Chainable + /** + * Check if current subject has a class + * @see https://on.cypress.io/should + * @see https://on.cypress.io/assertions + * @example + * cy.get('#result').should('have.class', 'success') + */ (chainer: 'have.class', value: string): Chainable (chainer: 'have.css', value: string, match?: string): Chainable (chainer: 'have.data', value: string, match?: string): Chainable @@ -1407,6 +1494,14 @@ declare namespace Cypress { (chainer: 'have.id', value: string, match?: string): Chainable (chainer: 'have.prop', value: string, match?: any): Chainable (chainer: 'have.text', value: string): Chainable + /** + * Check if current subject element has expected value + * @see https://on.cypress.io/should + * @see https://on.cypress.io/assertions + * @example + * // retry until this textarea has the correct value + * cy.get('textarea').should('have.value', 'foo bar baz') + */ (chainer: 'have.value', value: string): Chainable (chainer: 'match', value: string): Chainable @@ -1418,6 +1513,14 @@ declare namespace Cypress { (chainer: 'not.be.hidden'): Chainable (chainer: 'not.be.selected'): Chainable (chainer: 'not.be.visible'): Chainable + /** + * Check if current element does not have text value + * @see https://on.cypress.io/should + * @see https://on.cypress.io/assertions + * @example + * // retry until this span does not contain 'click me' + * cy.get('a').parent('span.help').should('not.contain', 'click me') + */ (chainer: 'not.contain', value: string): Chainable (chainer: 'not.exist'): Chainable (chainer: 'not.have.attr', value: string, match?: string): Chainable @@ -1525,10 +1628,24 @@ declare namespace Cypress { type ViewportPreset = 'macbook-15' | 'macbook-13' | 'macbook-11' | 'ipad-2' | 'ipad-mini' | 'iphone-6+' | 'iphone-6' | 'iphone-5' | 'iphone-4' | 'iphone-3' // Diff / Omit taken from https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-311923766 - type Diff = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T] + type Diff = ({[P in T]: P } & {[P in U]: never } & { [x: string]: never })[T] type Omit = Pick> } -// global variables added by Cypress when it runs +/** + * Global variables `cy` added by Cypress with all API commands. + * @see https://on.cypress.io/api + * @example + * cy.get('button').click() + * cy.get('.result').contains('Expected text') + */ declare const cy: Cypress.Chainable +/** + * Global variable `Cypress` holds common utilities and constants. + * @see https://on.cypress.io/api + * @example + * Cypress.config("pageLoadTimeout") // => 60000 + * Cypress.version // => "1.4.0" + * Cypress._ // => Lodash _ + */ declare const Cypress: Cypress.Cypress From 628e50c69d725ed2914ce06ee208864354915700 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Fri, 22 Dec 2017 12:00:13 -0500 Subject: [PATCH 2/4] document more common methods --- cli/types/index.d.ts | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/cli/types/index.d.ts b/cli/types/index.d.ts index a3f5a276d79e..ab393458c0b7 100644 --- a/cli/types/index.d.ts +++ b/cli/types/index.d.ts @@ -220,8 +220,23 @@ declare namespace Cypress { * Check checkbox(es) or radio(s). This element must be an `` with type `checkbox` or `radio`. * * @see https://on.cypress.io/check + * @example + * // Check checkbox element + * cy.get('[type="checkbox"]').check() + * // Check first radio element + * cy.get('[type="radio"]').first().check() */ check(options?: Partial): Chainable + /** + * Check checkbox(es) or radio(s). This element must be an `` with type `checkbox` or `radio`. + * + * @see https://on.cypress.io/check + * @example + * // Select the radio with the value of ‘US’ + * cy.get('[type="radio"]').check('US') + * // Check the checkboxes with the values ‘ga’ and ‘ca’ + * cy.get('[type="checkbox"]').check(['ga', 'ca']) + */ check(value: string | string[], options?: Partial): Chainable /** @@ -306,6 +321,15 @@ declare namespace Cypress { * Get the DOM element containing the text. DOM elements can contain more than the desired text and still match. Additionally, Cypress prefers some DOM elements over the deepest element found. * * @see https://on.cypress.io/contains + * @example + * // Yield el in .nav containing 'About' + * cy.get('.nav').contains('About') + * // Yield first el in document containing 'Hello' + * cy.contains('Hello') + * // you can use regular expression + * cy.contains(/^b\w+/) + * // yields
      ...
    + * cy.contains('ul', 'apples') */ contains(content: string | number | RegExp): Chainable contains(content: string | number | RegExp): Chainable> @@ -358,6 +382,11 @@ declare namespace Cypress { * Get A DOM element at a specific index in an array of elements. * * @see https://on.cypress.io/eq + * @param {Number} index A number indicating the index to find the element at within an array of elements. A negative number counts index from the end of the list. + * @example + * cy.get('tbody>tr').eq(0) // Yield first 'tr' in 'tbody' + * cy.get('ul>li').eq('4') // Yield fifth 'li' in 'ul' + * cy.get('li').eq(-2) // Yields second from last 'li' element */ eq(index: number, options?: Partial): Chainable> @@ -379,6 +408,8 @@ declare namespace Cypress { * Get the descendent DOM elements of a specific selector. * * @see https://on.cypress.io/find + * @example + * cy.get('.article').find('footer') // Yield 'footer' within '.article' */ find(selector: K, options?: Partial): Chainable> find(selector: string, options?: Partial): Chainable> @@ -796,6 +827,8 @@ declare namespace Cypress { * @see https://on.cypress.io/type * @example * cy.get('input').type('Hello, World') + * // type "hello" + press Enter + * cy.get('input').type('hello{enter}') */ type(text: string, options?: Partial): Chainable @@ -1302,6 +1335,13 @@ declare namespace Cypress { (chainer: 'be.undefined'): Chainable (chainer: 'be.within', start: number, end: number): Chainable (chainer: 'change', value: object, property: string): Chainable + /** + * Check if current element contains given text + * @see https://on.cypress.io/should + * @see https://on.cypress.io/assertions + * @example + * cy.get('.greeting').should('contain', 'world') + */ (chainer: 'contain', value: any): Chainable (chainer: 'decrease', value: object, property: string): Chainable (chainer: 'deep.equal', value: Subject): Chainable From c0db552e956cc80060d0b6dc5028e0791d56b603 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Fri, 22 Dec 2017 12:11:58 -0500 Subject: [PATCH 3/4] more comments for #1117 --- cli/types/index.d.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/cli/types/index.d.ts b/cli/types/index.d.ts index ab393458c0b7..ec2adde011a4 100644 --- a/cli/types/index.d.ts +++ b/cli/types/index.d.ts @@ -204,8 +204,17 @@ declare namespace Cypress { * Assign an alias for later use. Reference the alias later within a * [cy.get()](https://on.cypress.io/get) or * [cy.wait()](https://on.cypress.io/wait) command with a `@` prefix. + * You can alias DOM elements, routes, stubs and spies. * * @see https://on.cypress.io/as + * @see https://on.cypress.io/variables-and-aliases + * @see https://on.cypress.io/get + * @example + * // Get the aliased ‘todos’ elements + * cy.get('ul#todos').as('todos') + * //...hack hack hack... + * // later retrieve the todos + * cy.get('@todos') */ as(alias: string): Chainable @@ -660,8 +669,20 @@ declare namespace Cypress { * Reload the page. * * @see https://on.cypress.io/reload + * @example + * cy.reload() */ reload(options?: Partial): Chainable + /** + * Reload the page without cache + * + * @see https://on.cypress.io/reload + * @param {Boolean} forceReload Whether to reload the current page without using the cache. true forces the reload without cache. + * @example + * // Reload the page without using the cache + * cy.visit('http://localhost:3000/admin') + * cy.reload(true) + */ reload(forceReload: boolean): Chainable /** From 8a93da848f41ddc2091d382e81cca9609fec9c1a Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Fri, 22 Dec 2017 12:20:52 -0500 Subject: [PATCH 4/4] todomvc examplehas good intellisense, close #1117 --- cli/types/index.d.ts | 52 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/cli/types/index.d.ts b/cli/types/index.d.ts index ec2adde011a4..e7f31c40fef6 100644 --- a/cli/types/index.d.ts +++ b/cli/types/index.d.ts @@ -294,9 +294,32 @@ declare namespace Cypress { * Click a DOM element. * * @see https://on.cypress.io/click + * @example + * cy.get('button').click() // Click on button + * cy.focused().click() // Click on el with focus + * cy.contains('Welcome').click() // Click on first el containing 'Welcome' */ click(options?: Partial): Chainable + /** + * Click a DOM element at specific corner / side. + * + * @param {String} position The position where the click should be issued. The `center` position is the default position. + * @see https://on.cypress.io/click + * @example + * cy.get('button').click('topRight') + */ click(position: string, options?: Partial): Chainable + /** + * Click a DOM element at specific coordinates + * + * @param {number} x The distance in pixels from the element’s left to issue the click. + * @param {number} y The distance in pixels from the element’s top to issue the click. + * @see https://on.cypress.io/click + * @example + * // The click below will be issued inside of the element + * // (15px from the left and 40px from the top). + * cy.get('button').click(15, 40) + */ click(x: number, y: number, options?: Partial): Chainable /** @@ -812,6 +835,14 @@ declare namespace Cypress { then(fn: (this: ObjectLike, currentSubject: Subject) => Chainable, options?: Partial): Chainable then(fn: (this: ObjectLike, currentSubject: Subject) => PromiseLike, options?: Partial): Chainable then(fn: (this: ObjectLike, currentSubject: Subject) => S, options?: Partial): Chainable + /** + * Enables you to work with the subject yielded from the previous command. + * + * @see https://on.cypress.io/then + * @example + * cy.get('.nav').then(($nav) => {}) // Yields .nav as first arg + * cy.location().then((loc) => {}) // Yields location object as first arg + */ then(fn: (this: ObjectLike, currentSubject: Subject) => void, options?: Partial): Chainable /** @@ -905,19 +936,26 @@ declare namespace Cypress { * * @see https://on.cypress.io/window * @example window - * ```ts - * cy.visit('http://localhost:8080/app') - * cy.window().then(function(win){ - * // win is the remote window - * // of the page at: http://localhost:8080/app - * }) - * ``` + * cy.visit('http://localhost:8080/app') + * cy.window().then(function(win){ + * // win is the remote window + * // of the page at: http://localhost:8080/app + * }) */ window(options?: Partial): Chainable /** * Scopes all subsequent cy commands to within this element. Useful when working within a particular group of elements such as a `
    `. * @see https://on.cypress.io/within + * @example + * cy.get('form').within(($form) => { + * // cy.get() will only search for elements within form, + * // not within the entire document + * cy.get('input[name="username"]').type('john') + * cy.get('input[name="password"]').type('password') + * cy.root().submit() + * }) + * */ within(fn: (currentSubject: Subject) => void): Chainable within(options: Partial, fn: (currentSubject?: Subject) => void): Chainable // inconsistent argument order