diff --git a/packages/driver/src/cy/keyboard.ts b/packages/driver/src/cy/keyboard.ts
index 198cfaea1f89..588204d0c785 100644
--- a/packages/driver/src/cy/keyboard.ts
+++ b/packages/driver/src/cy/keyboard.ts
@@ -211,7 +211,7 @@ const getKeyDetails = (onKeyNotFound) => {
onKeyNotFound(key, _.keys(getKeymap()).join(', '))
- throw Error(`Not a valid key: ${key}`)
+ throw new Error('this can never happen')
}
}
@@ -280,7 +280,8 @@ const validateTyping = (
keys: KeyDetails[],
currentIndex: number,
onFail: Function,
- skipCheckUntilIndex?: number,
+ skipCheckUntilIndex: number | undefined,
+ force: boolean
) => {
const chars = joinKeyArrayToString(keys.slice(currentIndex))
const allChars = joinKeyArrayToString(keys)
@@ -328,15 +329,20 @@ const validateTyping = (
return {}
}
- if (!isFocusable) {
+ // throw error if element, which is normally typeable, is disabled for some reason
+ // don't throw if force: true
+ if (!isFocusable && isTextLike && !force) {
const node = $dom.stringify($el)
- if (isTextLike) {
- $utils.throwErrByPath('type.not_actionable_textlike', {
- onFail,
- args: { node },
- })
- }
+ $utils.throwErrByPath('type.not_actionable_textlike', {
+ onFail,
+ args: { node },
+ })
+ }
+
+ // throw error if element cannot receive keyboard events under any conditions
+ if (!isFocusable && !isTextLike) {
+ const node = $dom.stringify($el)
$utils.throwErrByPath('type.not_on_typeable_element', {
onFail,
@@ -660,6 +666,7 @@ export class Keyboard {
currentKeyIndex,
options.onFail,
_skipCheckUntilIndex,
+ options.force
)
_skipCheckUntilIndex = skipCheckUntilIndex
diff --git a/packages/driver/src/dom/selection.ts b/packages/driver/src/dom/selection.ts
index 5f210a3e9679..bf70d7522d6c 100644
--- a/packages/driver/src/dom/selection.ts
+++ b/packages/driver/src/dom/selection.ts
@@ -154,6 +154,13 @@ const setSelectionRange = function (el, start, end) {
$elements.callNativeMethod(el, 'setSelectionRange', start, end)
}
+// Whether or not the selection contains any text
+// since Selection.isCollapsed will be true when selection
+// is inside non-selectionRange input (e.g. input[type=email])
+const isSelectionCollapsed = function (selection: Selection) {
+ return !selection.toString()
+}
+
const deleteRightOfCursor = function (el) {
if ($elements.canSetSelectionRangeElement(el)) {
const { start, end } = getSelectionBounds(el)
@@ -167,7 +174,7 @@ const deleteRightOfCursor = function (el) {
const selection = _getSelectionByEl(el)
- if (selection.isCollapsed) {
+ if (isSelectionCollapsed(selection)) {
$elements.callNativeMethod(
selection,
'modify',
@@ -195,7 +202,7 @@ const deleteLeftOfCursor = function (el) {
const selection = _getSelectionByEl(el)
- if (selection.isCollapsed) {
+ if (isSelectionCollapsed(selection)) {
$elements.callNativeMethod(
selection,
'modify',
diff --git a/packages/driver/test/cypress/integration/commands/actions/type_spec.js b/packages/driver/test/cypress/integration/commands/actions/type_spec.js
index 0fa74a8cf242..0b518ecd7a05 100644
--- a/packages/driver/test/cypress/integration/commands/actions/type_spec.js
+++ b/packages/driver/test/cypress/integration/commands/actions/type_spec.js
@@ -183,7 +183,7 @@ describe('src/cy/commands/actions/type', () => {
})
describe('actionability', () => {
- it('can forcibly click even when element is invisible', () => {
+ it('can forcibly type + click even when element is invisible', () => {
const $txt = cy.$$(':text:first').hide()
expect($txt).not.to.have.value('foo')
@@ -199,7 +199,13 @@ describe('src/cy/commands/actions/type', () => {
})
})
- it('can forcibly click even when being covered by another element', () => {
+ it('can force type when element is disabled', function () {
+ cy.$$('input:text:first').prop('disabled', true)
+ cy.get('input:text:first').type('foo', { force: true })
+ .should('have.value', 'foo')
+ })
+
+ it('can forcibly type + click even when being covered by another element', () => {
const $input = $('')
.attr('id', 'input-covered-in-span')
.css({
@@ -1810,6 +1816,13 @@ describe('src/cy/commands/actions/type', () => {
})
})
+ it('can delete all with {selectall}{backspace} in non-selectionrange element', () => {
+ cy.get('input[type=email]:first')
+ .should(($el) => $el.val('sdfsdf'))
+ .type('{selectall}{backspace}')
+ .should('have.value', '')
+ })
+
it('can backspace a selection range of characters', () => {
// select the 'ar' characters
cy
@@ -4415,22 +4428,6 @@ describe('src/cy/commands/actions/type', () => {
cy.get('input:text:first').type('foo')
})
- it('throws when subject is disabled and force:true', function (done) {
- cy.timeout(200)
-
- cy.$$('input:text:first').prop('disabled', true)
-
- cy.on('fail', (err) => {
- // get + type logs
- expect(this.logs.length).eq(2)
- expect(err.message).to.include('cy.type() failed because it targeted a disabled element.')
-
- done()
- })
-
- cy.get('input:text:first').type('foo', { force: true })
- })
-
it('throws when submitting within nested forms')
it('logs once when not dom subject', function (done) {
@@ -4532,11 +4529,11 @@ https://on.cypress.io/type`)
})
})
- describe('naughtly strings', () => {
+ describe('naughty strings', () => {
_.each(['Ω≈ç√∫˜µ≤≥÷', '2.2250738585072011e-308', '田中さんにあげて下さい',
'', '⁰⁴⁵₀₁₂', '🐵 🙈 🙉 🙊',
'', '$USER'], (val) => {
- it(`allows typing some naughtly strings (${val})`, () => {
+ it(`allows typing some naughty strings (${val})`, () => {
cy
.get(':text:first').type(val)
.should('have.value', val)