Skip to content

Commit

Permalink
feat: Add keystrokeDelay (#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
herecydev and chrisbreiding authored Jun 16, 2021
1 parent 929cac8 commit ce5abe4
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

4 comments on commit ce5abe4

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on ce5abe4 Jun 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the linux x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/7.6.0/circle-develop-ce5abe48c0593a3db948773ea23334e7fe581db4/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on ce5abe4 Jun 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppVeyor has built the win32 x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/7.6.0/appveyor-develop-ce5abe48c0593a3db948773ea23334e7fe581db4/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on ce5abe4 Jun 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppVeyor has built the win32 ia32 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/7.6.0/appveyor-develop-ce5abe48c0593a3db948773ea23334e7fe581db4/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on ce5abe4 Jun 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the darwin x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/7.6.0/circle-develop-ce5abe48c0593a3db948773ea23334e7fe581db4/cypress.tgz

Please sign in to comment.