Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(driver): add select by index #18201

Merged
merged 14 commits into from
Sep 24, 2021
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 87 additions & 93 deletions cli/types/cypress.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ declare namespace Cypress {
/**
* The interface for user-defined properties in Window object under test.
*/
interface ApplicationWindow {} // tslint:disable-line
interface ApplicationWindow { } // tslint:disable-line

/**
* Several libraries are bundled with Cypress by default.
Expand Down Expand Up @@ -521,7 +521,7 @@ declare namespace Cypress {
/**
* @see https://on.cypress.io/keyboard-api
*/
Keyboard: {
Keyboard: {
defaults(options: Partial<KeyboardDefaultsOptions>): void
}

Expand Down Expand Up @@ -579,7 +579,7 @@ declare namespace Cypress {
}

interface SessionOptions {
validate?: () => false|void
validate?: () => false | void
}

type CanReturnChainable = void | Chainable | Promise<unknown>
Expand Down Expand Up @@ -717,36 +717,36 @@ declare namespace Cypress {
```
*/
clearLocalStorage(re: RegExp): Chainable<Storage>
/**
* Clear data in local storage.
* Cypress automatically runs this command before each test to prevent state from being
* shared across tests. You shouldn’t need to use this command unless you’re using it
* to clear localStorage inside a single test. Yields `localStorage` object.
*
* @see https://on.cypress.io/clearlocalstorage
* @param {options} [object] - options object
* @example
```
// Removes all local storage items, without logging
cy.clearLocalStorage({ log: false })
```
*/
/**
* Clear data in local storage.
* Cypress automatically runs this command before each test to prevent state from being
* shared across tests. You shouldn’t need to use this command unless you’re using it
* to clear localStorage inside a single test. Yields `localStorage` object.
*
* @see https://on.cypress.io/clearlocalstorage
* @param {options} [object] - options object
* @example
```
// Removes all local storage items, without logging
cy.clearLocalStorage({ log: false })
```
*/
clearLocalStorage(options: Partial<Loggable>): Chainable<Storage>
/**
* Clear data in local storage.
* Cypress automatically runs this command before each test to prevent state from being
* shared across tests. You shouldn’t need to use this command unless you’re using it
* to clear localStorage inside a single test. Yields `localStorage` object.
*
* @see https://on.cypress.io/clearlocalstorage
* @param {string} [key] - name of a particular item to remove (optional).
* @param {options} [object] - options object
* @example
```
// Removes item "todos" without logging
cy.clearLocalStorage("todos", { log: false })
```
*/
/**
* Clear data in local storage.
* Cypress automatically runs this command before each test to prevent state from being
* shared across tests. You shouldn’t need to use this command unless you’re using it
* to clear localStorage inside a single test. Yields `localStorage` object.
*
* @see https://on.cypress.io/clearlocalstorage
* @param {string} [key] - name of a particular item to remove (optional).
* @param {options} [object] - options object
* @example
```
// Removes item "todos" without logging
cy.clearLocalStorage("todos", { log: false })
```
*/
clearLocalStorage(key: string, options: Partial<Loggable>): Chainable<Storage>

/**
Expand Down Expand Up @@ -834,7 +834,7 @@ declare namespace Cypress {
* // or use this shortcut
* cy.clock().invoke('restore')
*/
clock(now: number|Date, options?: Loggable): Chainable<Clock>
clock(now: number | Date, options?: Loggable): Chainable<Clock>
/**
* Mocks global clock but only overrides specific functions.
*
Expand All @@ -843,7 +843,7 @@ declare namespace Cypress {
* // keep current date but override "setTimeout" and "clearTimeout"
* cy.clock(null, ['setTimeout', 'clearTimeout'])
*/
clock(now: number|Date, functions?: Array<'setTimeout' | 'clearTimeout' | 'setInterval' | 'clearInterval' | 'Date'>, options?: Loggable): Chainable<Clock>
clock(now: number | Date, functions?: Array<'setTimeout' | 'clearTimeout' | 'setInterval' | 'clearInterval' | 'Date'>, options?: Loggable): Chainable<Clock>
/**
* Mocks global clock and all functions.
*
Expand Down Expand Up @@ -977,14 +977,14 @@ declare namespace Cypress {
*/
debug(options?: Partial<Loggable>): Chainable<Subject>

/**
* Save/Restore browser Cookies, LocalStorage, and SessionStorage data resulting from the supplied `setup` function.
*
* Only available if the `experimentalSessionSupport` config option is enabled.
*
* @see https://on.cypress.io/session
*/
session(id: string|object, setup?: SessionOptions['validate'], options?: SessionOptions): Chainable<null>
/**
* Save/Restore browser Cookies, LocalStorage, and SessionStorage data resulting from the supplied `setup` function.
*
* Only available if the `experimentalSessionSupport` config option is enabled.
*
* @see https://on.cypress.io/session
*/
session(id: string | object, setup?: SessionOptions['validate'], options?: SessionOptions): Chainable<null>

/**
* Get the window.document of the page that is currently active.
Expand Down Expand Up @@ -1648,17 +1648,11 @@ declare namespace Cypress {
scrollTo(x: number | string, y: number | string, options?: Partial<ScrollToOptions>): Chainable<Subject>

/**
* Select an `<option>` with specific text within a `<select>`.
*
* @see https://on.cypress.io/select
*/
select(text: string | string[], options?: Partial<SelectOptions>): Chainable<Subject>
/**
* Select an `<option>` with specific value(s) within a `<select>`.
* Select an `<option>` with specific text, value, or index within a `<select>`.
*
* @see https://on.cypress.io/select
*/
select(value: string | string[], options?: Partial<SelectOptions>): Chainable<Subject>
select(valueOrTextOrIndex: string | number | Array<string | number>, options?: Partial<SelectOptions>): Chainable<Subject>

/**
* @deprecated Use `cy.intercept()` instead.
Expand Down Expand Up @@ -1909,13 +1903,13 @@ declare namespace Cypress {
*
* @see https://on.cypress.io/then
*/
then<S extends HTMLElement>(options: Partial<Timeoutable>, fn: (this: ObjectLike, currentSubject: Subject) => S): Chainable<JQuery<S>>
/**
* Enables you to work with the subject yielded from the previous command / promise.
*
* @see https://on.cypress.io/then
*/
then<S extends ArrayLike<HTMLElement>>(options: Partial<Timeoutable>, fn: (this: ObjectLike, currentSubject: Subject) => S): Chainable<JQuery<S extends ArrayLike<infer T> ? T : never>>
then<S extends HTMLElement>(options: Partial<Timeoutable>, fn: (this: ObjectLike, currentSubject: Subject) => S): Chainable<JQuery<S>>
/**
* Enables you to work with the subject yielded from the previous command / promise.
*
* @see https://on.cypress.io/then
*/
then<S extends ArrayLike<HTMLElement>>(options: Partial<Timeoutable>, fn: (this: ObjectLike, currentSubject: Subject) => S): Chainable<JQuery<S extends ArrayLike<infer T> ? T : never>>
/**
* Enables you to work with the subject yielded from the previous command / promise.
*
Expand Down Expand Up @@ -2754,7 +2748,7 @@ declare namespace Cypress {
* To enable test retries only in runMode, set e.g. `{ openMode: null, runMode: 2 }`
* @default null
*/
retries: Nullable<number | {runMode?: Nullable<number>, openMode?: Nullable<number>}>
retries: Nullable<number | { runMode?: Nullable<number>, openMode?: Nullable<number> }>
/**
* Enables including elements within the shadow DOM when using querying
* commands (e.g. cy.get(), cy.find()). Can be set globally in cypress.json,
Expand Down Expand Up @@ -2891,7 +2885,7 @@ declare namespace Cypress {
* All configuration items are optional.
*/
type CoreConfigOptions = Partial<Omit<ResolvedConfigOptions, TestingType>>
type ConfigOptions = CoreConfigOptions & {e2e?: CoreConfigOptions, component?: CoreConfigOptions }
type ConfigOptions = CoreConfigOptions & { e2e?: CoreConfigOptions, component?: CoreConfigOptions }

interface PluginConfigOptions extends ResolvedConfigOptions {
/**
Expand Down Expand Up @@ -5703,48 +5697,48 @@ declare namespace Cypress {
}
```
*/
interface cy extends Chainable<undefined> {}
interface cy extends Chainable<undefined> { }
}

declare namespace Mocha {
interface TestFunction {
/**
* Describe a specification or test-case with the given `title`, TestOptions, and callback `fn` acting
* as a thunk.
*/
(title: string, config: Cypress.TestConfigOverrides, fn?: Func): Test

/**
* Describe a specification or test-case with the given `title`, TestOptions, and callback `fn` acting
* as a thunk.
*/
(title: string, config: Cypress.TestConfigOverrides, fn?: AsyncFunc): Test
/**
* Describe a specification or test-case with the given `title`, TestOptions, and callback `fn` acting
* as a thunk.
*/
(title: string, config: Cypress.TestConfigOverrides, fn?: Func): Test

/**
* Describe a specification or test-case with the given `title`, TestOptions, and callback `fn` acting
* as a thunk.
*/
(title: string, config: Cypress.TestConfigOverrides, fn?: AsyncFunc): Test
}
interface ExclusiveTestFunction {
/**
* Describe a specification or test-case with the given `title`, TestOptions, and callback `fn` acting
* as a thunk.
*/
(title: string, config: Cypress.TestConfigOverrides, fn?: Func): Test

/**
* Describe a specification or test-case with the given `title`, TestOptions, and callback `fn` acting
* as a thunk.
*/
(title: string, config: Cypress.TestConfigOverrides, fn?: AsyncFunc): Test
/**
* Describe a specification or test-case with the given `title`, TestOptions, and callback `fn` acting
* as a thunk.
*/
(title: string, config: Cypress.TestConfigOverrides, fn?: Func): Test

/**
* Describe a specification or test-case with the given `title`, TestOptions, and callback `fn` acting
* as a thunk.
*/
(title: string, config: Cypress.TestConfigOverrides, fn?: AsyncFunc): Test
}
interface PendingTestFunction {
/**
* Describe a specification or test-case with the given `title`, TestOptions, and callback `fn` acting
* as a thunk.
*/
(title: string, config: Cypress.TestConfigOverrides, fn?: Func): Test

/**
* Describe a specification or test-case with the given `title`, TestOptions, and callback `fn` acting
* as a thunk.
*/
(title: string, config: Cypress.TestConfigOverrides, fn?: AsyncFunc): Test
/**
* Describe a specification or test-case with the given `title`, TestOptions, and callback `fn` acting
* as a thunk.
*/
(title: string, config: Cypress.TestConfigOverrides, fn?: Func): Test

/**
* Describe a specification or test-case with the given `title`, TestOptions, and callback `fn` acting
* as a thunk.
*/
(title: string, config: Cypress.TestConfigOverrides, fn?: AsyncFunc): Test
}

interface SuiteFunction {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ describe('src/cy/commands/actions/select', () => {
})
})

it('selects by index', () => {
cy.get('select[name=maps]').select(2).then(($select) => {
expect($select).to.have.value('de_nuke')
})
})

it('selects by trimmed text with newlines stripped', () => {
cy.get('select[name=maps]').select('italy').then(($select) => {
expect($select).to.have.value('cs_italy')
Expand Down Expand Up @@ -88,6 +94,18 @@ describe('src/cy/commands/actions/select', () => {
})
})

it('can select an array of indexes', () => {
cy.get('select[name=movies]').select([1, 5]).then(($select) => {
expect($select.val()).to.deep.eq(['thc', 'twbb'])
})
})

it('can select an array of same value and index', () => {
cy.get('select[name=movies]').select(['thc', 1]).then(($select) => {
expect($select.val()).to.deep.eq(['thc'])
})
})

// readonly should only be limited to inputs, not checkboxes
it('can select a readonly select', () => {
cy.get('select[name=hunter]').select('gon').then(($select) => {
Expand Down Expand Up @@ -359,6 +377,39 @@ describe('src/cy/commands/actions/select', () => {
cy.get('input:first').select('foo')
})

it('throws on negative index', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include('`cy.select()` was called with an invalid index: `-1`. Index must be a non-negative integer.')
expect(err.docsUrl).to.eq('https://on.cypress.io/select')

done()
})

cy.get('select:first').select(-1)
})

it('throws on non-integer index', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include('`cy.select()` was called with an invalid index: `1.5`. Index must be a non-negative integer.')
expect(err.docsUrl).to.eq('https://on.cypress.io/select')

done()
})

cy.get('select:first').select(1.5)
})

it('throws on out-of-range index', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include('`cy.select()` failed because it could not find a single `<option>` with value, index, or text matching: `3`')
expect(err.docsUrl).to.eq('https://on.cypress.io/select')

done()
})

cy.get('select[name=foods]').select(3)
})

it('throws when finding duplicate values', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include('`cy.select()` matched more than one `option` by value or text: `bm`')
Expand Down Expand Up @@ -395,7 +446,7 @@ describe('src/cy/commands/actions/select', () => {

it('throws when value or text does not exist', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include('`cy.select()` failed because it could not find a single `<option>` with value or text matching: `foo`')
expect(err.message).to.include('`cy.select()` failed because it could not find a single `<option>` with value, index, or text matching: `foo`')
expect(err.docsUrl).to.eq('https://on.cypress.io/select')

done()
Expand Down Expand Up @@ -526,7 +577,7 @@ describe('src/cy/commands/actions/select', () => {
done()
})

cy.get('#select-maps').select('de_dust2').then(($select) => {})
cy.get('#select-maps').select('de_dust2').then(($select) => { })
})

it('snapshots after clicking', () => {
Expand Down
Loading