Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions lib/helper/Playwright.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import promiseRetry from 'promise-retry'
import Locator from '../locator.js'
import recorder from '../recorder.js'
import store from '../store.js'
import { checkFocusBeforeType, checkFocusBeforePressKey } from './extras/focusCheck.js'
import { includes as stringIncludes } from '../assert/include.js'
import { urlEquals, equals } from '../assert/equal.js'
import { empty } from '../assert/empty.js'
Expand Down Expand Up @@ -2231,6 +2232,7 @@ class Playwright extends Helper {
* {{> pressKeyWithKeyNormalization }}
*/
async pressKey(key) {
await checkFocusBeforePressKey(this, key)
const modifiers = []
if (Array.isArray(key)) {
for (let k of key) {
Expand Down Expand Up @@ -2259,6 +2261,8 @@ class Playwright extends Helper {
* {{> type }}
*/
async type(keys, delay = null) {
await checkFocusBeforeType(this)

// Always use page.keyboard.type for any string (including single character and national characters).
if (!Array.isArray(keys)) {
keys = keys.toString()
Expand Down
4 changes: 4 additions & 0 deletions lib/helper/Puppeteer.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import promiseRetry from 'promise-retry'
import Locator from '../locator.js'
import recorder from '../recorder.js'
import store from '../store.js'
import { checkFocusBeforeType, checkFocusBeforePressKey } from './extras/focusCheck.js'
import { includes as stringIncludes } from '../assert/include.js'
import { urlEquals, equals } from '../assert/equal.js'
import { empty } from '../assert/empty.js'
Expand Down Expand Up @@ -1547,6 +1548,7 @@ class Puppeteer extends Helper {
* {{> pressKeyWithKeyNormalization }}
*/
async pressKey(key) {
await checkFocusBeforePressKey(this, key)
const modifiers = []
if (Array.isArray(key)) {
for (let k of key) {
Expand Down Expand Up @@ -1575,6 +1577,8 @@ class Puppeteer extends Helper {
* {{> type }}
*/
async type(keys, delay = null) {
await checkFocusBeforeType(this)

if (!Array.isArray(keys)) {
keys = keys.toString()
keys = keys.split('')
Expand Down
4 changes: 4 additions & 0 deletions lib/helper/WebDriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import promiseRetry from 'promise-retry'
import { includes as stringIncludes } from '../assert/include.js'
import { urlEquals, equals } from '../assert/equal.js'
import store from '../store.js'
import { checkFocusBeforeType, checkFocusBeforePressKey } from './extras/focusCheck.js'
import output from '../output.js'
const { debug } = output
import { empty } from '../assert/empty.js'
Expand Down Expand Up @@ -2237,6 +2238,7 @@ class WebDriver extends Helper {
* {{> pressKeyWithKeyNormalization }}
*/
async pressKey(key) {
await checkFocusBeforePressKey(this, key)
const modifiers = []
if (Array.isArray(key)) {
for (let k of key) {
Expand Down Expand Up @@ -2283,6 +2285,8 @@ class WebDriver extends Helper {
* {{> type }}
*/
async type(keys, delay = null) {
await checkFocusBeforeType(this)

if (!Array.isArray(keys)) {
keys = keys.toString()
keys = keys.split('')
Expand Down
8 changes: 8 additions & 0 deletions lib/helper/errors/NonFocusedType.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class NonFocusedType extends Error {
constructor(message) {
super(message)
this.name = 'NonFocusedType'
}
}

export default NonFocusedType
43 changes: 43 additions & 0 deletions lib/helper/extras/focusCheck.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import store from '../../store.js'
import NonFocusedType from '../errors/NonFocusedType.js'

const MODIFIER_PATTERN = /^(control|ctrl|meta|cmd|command|commandorcontrol|ctrlorcommand)/i
const EDITING_KEYS = new Set(['a', 'c', 'x', 'v', 'z', 'y'])

async function isNoElementFocused(helper) {
return helper.executeScript(() => {
const ae = document.activeElement
return !ae || ae === document.documentElement || (ae === document.body && !ae.isContentEditable)
})
}

export async function checkFocusBeforeType(helper) {
if (!helper.options.strict && !store.debugMode) return
if (!await isNoElementFocused(helper)) return

const message = 'No element is in focus. Use I.click() or I.focus() to activate an element before typing.'
if (helper.options.strict) throw new NonFocusedType(message)
helper.debugSection('Warning', message)
}

export async function checkFocusBeforePressKey(helper, originalKey) {
if (!helper.options.strict && !store.debugMode) return
if (!Array.isArray(originalKey)) return

let hasCtrlOrMeta = false
let actionKey = null
for (const k of originalKey) {
if (MODIFIER_PATTERN.test(k)) {
hasCtrlOrMeta = true
} else {
actionKey = k
}
}
if (!hasCtrlOrMeta || !actionKey || !EDITING_KEYS.has(actionKey.toLowerCase())) return

if (!await isNoElementFocused(helper)) return

const message = `No element is in focus. Key combination with "${originalKey.join('+')}" may not work as expected. Use I.click() or I.focus() first.`
if (helper.options.strict) throw new NonFocusedType(message)
helper.debugSection('Warning', message)
}
49 changes: 49 additions & 0 deletions test/helper/webapi.js
Original file line number Diff line number Diff line change
Expand Up @@ -2331,6 +2331,55 @@ export function tests() {
expect(err.message).to.include('/html')
expect(err.message).to.include('Use a more specific locator')
})

it('should throw NonFocusedType error when typing without focus', async () => {
await I.amOnPage('/form/field')
I.options.strict = true
let err
try {
await I.type('test')
} catch (e) {
err = e
}
expect(err).to.exist
expect(err.constructor.name).to.equal('NonFocusedType')
expect(err.message).to.include('No element is in focus')
})

it('should not throw NonFocusedType when element is focused', async () => {
await I.amOnPage('/form/field')
I.options.strict = true
await I.click('Name')
await I.type('test')
await I.seeInField('Name', 'test')
})

it('should throw NonFocusedType for Ctrl+A without focus', async () => {
await I.amOnPage('/form/field')
I.options.strict = true
let err
try {
await I.pressKey(['Control', 'A'])
} catch (e) {
err = e
}
expect(err).to.exist
expect(err.constructor.name).to.equal('NonFocusedType')
expect(err.message).to.include('No element is in focus')
})

it('should not throw for Escape without focus', async () => {
await I.amOnPage('/form/field')
I.options.strict = true
await I.pressKey('Escape')
})

it('should not throw for Ctrl+A when element is focused', async () => {
await I.amOnPage('/form/field')
I.options.strict = true
await I.click('Name')
await I.pressKey(['Control', 'A'])
})
})

describe('#elementIndex step option', () => {
Expand Down