Skip to content

Commit

Permalink
feat: Add keystrokeDelay (cypress-io#15683)
Browse files Browse the repository at this point in the history
* Add keystrokeDelay

* Config tests

* Add proper comments

* Add some tests for configuring timeout

* Add tests for false values

* Few additional tests

* Rename test

* feat: add Cypress.Keyboard.defaults() for specifying default keystrokeDelay

* use test-configuration on link

* remove obsolete error message

* fix types and add type tests

* fix types tests

* fix type test

Co-authored-by: Chris Breiding <chrisbreiding@gmail.com>
  • Loading branch information
2 people authored and Calyhre committed Jun 22, 2021
1 parent 7725a1c commit faec34d
Show file tree
Hide file tree
Showing 7 changed files with 334 additions and 15 deletions.
20 changes: 20 additions & 0 deletions cli/types/cypress.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,13 @@ declare namespace Cypress {
getElementCoordinatesByPositionRelativeToXY(element: JQuery | HTMLElement, x: number, y: number): ElementPositioning
}

/**
* @see https://on.cypress.io/keyboard-api
*/
Keyboard: {
defaults(options: Partial<KeyboardDefaultsOptions>): void
}

/**
* @see https://on.cypress.io/api/api-server
*/
Expand Down Expand Up @@ -2786,6 +2793,7 @@ declare namespace Cypress {

interface TestConfigOverrides extends Partial<Pick<ConfigOptions, 'animationDistanceThreshold' | 'baseUrl' | 'defaultCommandTimeout' | 'env' | 'execTimeout' | 'includeShadowDom' | 'requestTimeout' | 'responseTimeout' | 'retries' | 'scrollBehavior' | 'taskTimeout' | 'viewportHeight' | 'viewportWidth' | 'waitForAnimations'>> {
browser?: IsBrowserMatcher | IsBrowserMatcher[]
keystrokeDelay?: number
}

/**
Expand Down Expand Up @@ -2836,6 +2844,18 @@ declare namespace Cypress {
env: object
}

/**
* Options for Cypress.Keyboard.defaults()
*/
interface KeyboardDefaultsOptions {
/**
* Time, in milliseconds, between each keystroke when typing. (Pass 0 to disable)
*
* @default 10
*/
keystrokeDelay: number
}

/**
* Full set of possible options for cy.request call
*/
Expand Down
33 changes: 26 additions & 7 deletions cli/types/tests/cypress-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -592,17 +592,19 @@ namespace CypressTestConfigOverridesTests {
browser: [{name: 'firefox'}, {name: 'chrome'}]
}, () => {})
it('test', {
browser: 'firefox'
browser: 'firefox',
keystrokeDelay: 0
}, () => {})
it('test', {
browser: {foo: 'bar'} // $ExpectError
browser: {foo: 'bar'}, // $ExpectError
}, () => {})

it('test', {
retries: null
retries: null,
keystrokeDelay: 0
}, () => { })
it('test', {
retries: 3
retries: 3,
keystrokeDelay: false, // $ExpectError
}, () => { })
it('test', {
retries: {
Expand Down Expand Up @@ -631,14 +633,16 @@ namespace CypressTestConfigOverridesTests {
// set config on a per-suite basis
describe('suite', {
browser: {family: 'firefox'},
baseUrl: 'www.example.com'
baseUrl: 'www.example.com',
keystrokeDelay: 0
}, () => {})

context('suite', {}, () => {})

describe('suite', {
browser: {family: 'firefox'},
baseUrl: 'www.example.com'
baseUrl: 'www.example.com',
keystrokeDelay: false // $ExpectError
foo: 'foo' // $ExpectError
}, () => {})

Expand Down Expand Up @@ -672,3 +676,18 @@ namespace CypressTaskTests {
val // $ExpectType unknown
})
}

namespace CypressKeyboardTests {
Cypress.Keyboard.defaults({
keystrokeDelay: 0
})
Cypress.Keyboard.defaults({
keystrokeDelay: 500
})
Cypress.Keyboard.defaults({
keystrokeDelay: false // $ExpectError
})
Cypress.Keyboard.defaults({
delay: 500 // $ExpectError
})
}
76 changes: 76 additions & 0 deletions packages/driver/cypress/integration/commands/actions/type_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,16 @@ describe('src/cy/commands/actions/type - #type', () => {
})

describe('delay', () => {
it('adds default delay to delta for each key sequence', () => {
cy.spy(cy, 'timeout')

cy.get(':text:first')
.type('foo{enter}bar{leftarrow}')
.then(() => {
expect(cy.timeout).to.be.calledWith(10 * 8, true, 'type')
})
})

it('adds delay to delta for each key sequence', () => {
cy.spy(cy, 'timeout')

Expand Down Expand Up @@ -667,6 +677,72 @@ describe('src/cy/commands/actions/type - #type', () => {

cy.get(':text:first').type('foo{enter}bar{leftarrow}')
})

it('test config keystrokeDelay overrides global value', { keystrokeDelay: 5 }, () => {
cy.spy(cy, 'timeout')

cy.get(':text:first')
.type('foo{enter}bar{leftarrow}')
.then(() => {
expect(cy.timeout).to.be.calledWith(5 * 8, true, 'type')
})
})

it('delay will override default keystrokeDelay', () => {
Cypress.Keyboard.defaults({
keystrokeDelay: 20,
})

cy.spy(cy, 'timeout')

cy.get(':text:first')
.type('foo{enter}bar{leftarrow}', { delay: 5 })
.then(() => {
expect(cy.timeout).to.be.calledWith(5 * 8, true, 'type')

Cypress.Keyboard.reset()
})
})

it('delay will override test config keystrokeDelay', { keystrokeDelay: 1000 }, () => {
cy.spy(cy, 'timeout')

cy.get(':text:first')
.type('foo{enter}bar{leftarrow}', { delay: 5 })
.then(() => {
expect(cy.timeout).to.be.calledWith(5 * 8, true, 'type')
})
})

it('does not increase the timeout delta when delay is 0', () => {
cy.spy(cy, 'timeout')

cy.get(':text:first').type('foo{enter}', { delay: 0 }).then(() => {
expect(cy.timeout).not.to.be.calledWith(0, true, 'type')
})
})

describe('errors', () => {
it('throws when delay is invalid', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.eq('`cy.type()` `delay` option must be 0 (zero) or a positive number. You passed: `false`')
expect(err.docsUrl).to.equal('https://on.cypress.io/type')
done()
})

cy.get(':text:first').type('foo', { delay: false })
})

it('throws when test config keystrokeDelay is invalid', { keystrokeDelay: false }, (done) => {
cy.on('fail', (err) => {
expect(err.message).to.eq('The test configuration `keystrokeDelay` option must be 0 (zero) or a positive number. You passed: `false`')
expect(err.docsUrl).to.equal('https://on.cypress.io/test-configuration')
done()
})

cy.get(':text:first').type('foo')
})
})
})

describe('events', () => {
Expand Down
108 changes: 108 additions & 0 deletions packages/driver/cypress/integration/cypress/keyboard_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
const { Keyboard } = Cypress

const DEFAULTS = {
keystrokeDelay: 10,
}

describe('src/cypress/keyboard', () => {
beforeEach(() => {
Keyboard.reset()
})

it('has defaults', () => {
expect(Keyboard.getConfig()).to.deep.eq(DEFAULTS)
})

context('.getConfig', () => {
it('returns config', () => {
expect(Keyboard.getConfig()).to.deep.eq(DEFAULTS)
})

it('does not allow mutation of config', () => {
const config = Keyboard.getConfig()

config.keystrokeDelay = 0

expect(Keyboard.getConfig().keystrokeDelay).to.eq(DEFAULTS.keystrokeDelay)
})
})

context('.defaults', () => {
it('is noop if not called with any valid properties', () => {
Keyboard.defaults({})
expect(Keyboard.getConfig()).to.deep.eq(DEFAULTS)
})

it('sets keystrokeDelay if specified', () => {
Keyboard.defaults({
keystrokeDelay: 5,
})

expect(Keyboard.getConfig().keystrokeDelay).to.eql(5)
})

it('returns new config', () => {
const result = Keyboard.defaults({
keystrokeDelay: 5,
})

expect(result).to.deep.eql({
keystrokeDelay: 5,
})
})

it('does not allow mutation via returned config', () => {
const result = Keyboard.defaults({
keystrokeDelay: 5,
})

result.keystrokeDelay = 0

expect(Keyboard.getConfig().keystrokeDelay).to.eq(5)
})

describe('errors', () => {
it('throws if not passed an object', () => {
const fn = () => {
Keyboard.defaults()
}

expect(fn).to.throw()
.with.property('message')
.and.eq('`Cypress.Keyboard.defaults()` must be called with an object. You passed: ``')

expect(fn).to.throw()
.with.property('docsUrl')
.and.eq('https://on.cypress.io/keyboard-api')
})

it('throws if keystrokeDelay is not a number', () => {
const fn = () => {
Keyboard.defaults({ keystrokeDelay: false })
}

expect(fn).to.throw()
.with.property('message')
.and.eq('`Cypress.Keyboard.defaults()` `keystrokeDelay` option must be 0 (zero) or a positive number. You passed: `false`')

expect(fn).to.throw()
.with.property('docsUrl')
.and.eq('https://on.cypress.io/keyboard-api')
})

it('throws if keystrokeDelay is a negative number', () => {
const fn = () => {
Keyboard.defaults({ keystrokeDelay: -10 })
}

expect(fn).to.throw()
.with.property('message')
.and.eq('`Cypress.Keyboard.defaults()` `keystrokeDelay` option must be 0 (zero) or a positive number. You passed: `-10`')

expect(fn).to.throw()
.with.property('docsUrl')
.and.eq('https://on.cypress.io/keyboard-api')
})
})
})
})
30 changes: 28 additions & 2 deletions packages/driver/src/cy/commands/actions/type.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ module.exports = function (Commands, Cypress, cy, state, config) {
log: true,
verify: true,
force: false,
delay: 10,
delay: config('keystrokeDelay') || $Keyboard.getConfig().keystrokeDelay,
release: true,
parseSpecialCharSequences: true,
waitForAnimations: config('waitForAnimations'),
Expand Down Expand Up @@ -130,6 +130,30 @@ module.exports = function (Commands, Cypress, cy, state, config) {
})
}

const isInvalidDelay = (delay) => {
return delay !== undefined && (!_.isNumber(delay) || delay < 0)
}

if (isInvalidDelay(userOptions.delay)) {
$errUtils.throwErrByPath('keyboard.invalid_delay', {
onFail: options._log,
args: {
cmd: 'type',
docsPath: 'type',
option: 'delay',
delay: userOptions.delay,
},
})
}

// specific error if test config keystrokeDelay is invalid
if (isInvalidDelay(config('keystrokeDelay'))) {
$errUtils.throwErrByPath('keyboard.invalid_per_test_delay', {
onFail: options._log,
args: { delay: config('keystrokeDelay') },
})
}

chars = `${chars}`

const win = state('window')
Expand Down Expand Up @@ -282,7 +306,9 @@ module.exports = function (Commands, Cypress, cy, state, config) {
// for the total number of keys we're about to
// type, ensure we raise the timeout to account
// for the delay being added to each keystroke
return cy.timeout(totalKeys * options.delay, true, 'type')
if (options.delay) {
return cy.timeout(totalKeys * options.delay, true, 'type')
}
},

onEvent: updateTable || _.noop,
Expand Down
Loading

0 comments on commit faec34d

Please sign in to comment.