diff --git a/cli/types/cypress.d.ts b/cli/types/cypress.d.ts
index 7031603da8fd..4a6424ee12fe 100644
--- a/cli/types/cypress.d.ts
+++ b/cli/types/cypress.d.ts
@@ -2319,6 +2319,54 @@ declare namespace Cypress {
* @default false
*/
multiple: boolean
+ /**
+ * Activates the control key during click
+ *
+ * @default false
+ */
+ ctrlKey: boolean
+ /**
+ * Activates the control key during click
+ *
+ * @default false
+ */
+ controlKey: boolean
+ /**
+ * Activates the alt key (option key for Mac) during click
+ *
+ * @default false
+ */
+ altKey: boolean
+ /**
+ * Activates the alt key (option key for Mac) during click
+ *
+ * @default false
+ */
+ optionKey: boolean
+ /**
+ * Activates the shift key during click
+ *
+ * @default false
+ */
+ shiftKey: boolean
+ /**
+ * Activates the meta key (Windows key or command key for Mac) during click
+ *
+ * @default false
+ */
+ metaKey: boolean
+ /**
+ * Activates the meta key (Windows key or command key for Mac) during click
+ *
+ * @default false
+ */
+ commandKey: boolean
+ /**
+ * Activates the meta key (Windows key or command key for Mac) during click
+ *
+ * @default false
+ */
+ cmdKey: boolean
}
interface ResolvedConfigOptions {
diff --git a/cli/types/tests/chainer-examples.ts b/cli/types/tests/chainer-examples.ts
index 7538a638b297..6f72b6f5af1a 100644
--- a/cli/types/tests/chainer-examples.ts
+++ b/cli/types/tests/chainer-examples.ts
@@ -467,6 +467,9 @@ cy.writeFile('../file.path', '', {
})
cy.get('foo').click()
+cy.get('foo').click({
+ ctrlKey: true,
+})
cy.get('foo').rightclick()
cy.get('foo').dblclick()
diff --git a/packages/driver/cypress/fixtures/issue-486.html b/packages/driver/cypress/fixtures/issue-486.html
new file mode 100644
index 000000000000..0f5954a16764
--- /dev/null
+++ b/packages/driver/cypress/fixtures/issue-486.html
@@ -0,0 +1,33 @@
+
+
+
+ Issue 486
+
+
+
+ Result
+
+
+
diff --git a/packages/driver/cypress/integration/commands/actions/click_spec.js b/packages/driver/cypress/integration/commands/actions/click_spec.js
index deb601cfa0a3..674c299ade04 100644
--- a/packages/driver/cypress/integration/commands/actions/click_spec.js
+++ b/packages/driver/cypress/integration/commands/actions/click_spec.js
@@ -882,6 +882,105 @@ describe('src/cy/commands/actions/click', () => {
})
})
+ describe('modifier options', () => {
+ beforeEach(() => {
+ cy.visit('/fixtures/issue-486.html')
+ })
+
+ it('ctrl', () => {
+ cy.get('#button').click({
+ ctrlKey: true,
+ })
+
+ cy.get('#result').should('contain', '{Ctrl}')
+
+ // ctrl should be released
+ cy.get('#button').click()
+ cy.get('#result').should('not.contain', '{Ctrl}')
+
+ cy.get('#button').click({
+ controlKey: true,
+ })
+
+ cy.get('#result').should('contain', '{Ctrl}')
+ })
+
+ it('alt', () => {
+ cy.get('#button').click({
+ altKey: true,
+ })
+
+ cy.get('#result').should('contain', '{Alt}')
+
+ // alt should be released
+ cy.get('#button').click()
+ cy.get('#result').should('not.contain', '{Alt}')
+
+ cy.get('#button').click({
+ optionKey: true,
+ })
+
+ cy.get('#result').should('contain', '{Alt}')
+ })
+
+ it('shift', () => {
+ cy.get('#button').click({
+ shiftKey: true,
+ })
+
+ cy.get('#result').should('contain', '{Shift}')
+
+ // shift should be released
+ cy.get('#button').click()
+ cy.get('#result').should('not.contain', '{Shift}')
+ })
+
+ it('meta', () => {
+ cy.get('#button').click({
+ metaKey: true,
+ })
+
+ cy.get('#result').should('contain', '{Meta}')
+
+ // shift should be released
+ cy.get('#button').click()
+ cy.get('#result').should('not.contain', '{Meta}')
+
+ cy.get('#button').click({
+ commandKey: true,
+ })
+
+ cy.get('#result').should('contain', '{Meta}')
+
+ cy.get('#button').click({
+ cmdKey: true,
+ })
+
+ cy.get('#result').should('contain', '{Meta}')
+ })
+
+ it('multiple', () => {
+ cy.get('#button').click({
+ ctrlKey: true,
+ altKey: true,
+ shiftKey: true,
+ metaKey: true,
+ })
+
+ cy.get('#result').should('contain', '{Ctrl}')
+ cy.get('#result').should('contain', '{Alt}')
+ cy.get('#result').should('contain', '{Shift}')
+ cy.get('#result').should('contain', '{Meta}')
+
+ // modifiers should be released
+ cy.get('#button').click()
+ cy.get('#result').should('not.contain', '{Ctrl}')
+ cy.get('#result').should('not.contain', '{Alt}')
+ cy.get('#result').should('not.contain', '{Shift}')
+ cy.get('#result').should('not.contain', '{Meta}')
+ })
+ })
+
describe('pointer-events:none', () => {
beforeEach(function () {
cy.$$('behind #ptrNone
').appendTo(cy.$$('#dom'))
diff --git a/packages/driver/src/cy/commands/actions/click.js b/packages/driver/src/cy/commands/actions/click.js
index db6ba8de67d3..09ead09029f5 100644
--- a/packages/driver/src/cy/commands/actions/click.js
+++ b/packages/driver/src/cy/commands/actions/click.js
@@ -34,7 +34,7 @@ const formatMouseEvents = (events) => {
}
module.exports = (Commands, Cypress, cy, state, config) => {
- const { mouse } = cy.devices
+ const { mouse, keyboard } = cy.devices
const mouseAction = (eventName, { subject, positionOrX, y, userOptions, onReady, onTable, defaultOptions }) => {
let position
@@ -54,6 +54,14 @@ module.exports = (Commands, Cypress, cy, state, config) => {
errorOnSelect: true,
waitForAnimations: config('waitForAnimations'),
animationDistanceThreshold: config('animationDistanceThreshold'),
+ ctrlKey: false,
+ controlKey: false,
+ altKey: false,
+ optionKey: false,
+ shiftKey: false,
+ metaKey: false,
+ commandKey: false,
+ cmdKey: false,
...defaultOptions,
})
@@ -65,6 +73,24 @@ module.exports = (Commands, Cypress, cy, state, config) => {
})
}
+ const flagModifiers = (press) => {
+ if (options.ctrlKey || options.controlKey) {
+ keyboard.flagModifier({ key: 'Control' }, press)
+ }
+
+ if (options.altKey || options.optionKey) {
+ keyboard.flagModifier({ key: 'Alt' }, press)
+ }
+
+ if (options.shiftKey) {
+ keyboard.flagModifier({ key: 'Shift' }, press)
+ }
+
+ if (options.metaKey || options.commandKey || options.cmdKey) {
+ keyboard.flagModifier({ key: 'Meta' }, press)
+ }
+ }
+
const perform = (el) => {
let deltaOptions
const $el = $dom.wrap(el)
@@ -158,8 +184,12 @@ module.exports = (Commands, Cypress, cy, state, config) => {
const moveEvents = mouse.move(fromElViewport, forceEl)
+ flagModifiers(true)
+
const onReadyProps = onReady(fromElViewport, forceEl)
+ flagModifiers(false)
+
return createLog({
moveEvents,
...onReadyProps,