Skip to content

Commit be3cbc6

Browse files
DavertMikclaude
andcommitted
feat: add focus detection before type() to warn or throw when no element is focused
type() sends keystrokes via page.keyboard which silently drops input when no element has focus. Add a shared checkFocusBeforeType() that warns in debug mode and throws NonFocusedType in strict mode. Applied to Playwright, Puppeteer, and WebDriver helpers. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent eef9817 commit be3cbc6

6 files changed

Lines changed: 63 additions & 0 deletions

File tree

lib/helper/Playwright.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import promiseRetry from 'promise-retry'
77
import Locator from '../locator.js'
88
import recorder from '../recorder.js'
99
import store from '../store.js'
10+
import { checkFocusBeforeType } from './extras/focusCheck.js'
1011
import { includes as stringIncludes } from '../assert/include.js'
1112
import { urlEquals, equals } from '../assert/equal.js'
1213
import { empty } from '../assert/empty.js'
@@ -2259,6 +2260,8 @@ class Playwright extends Helper {
22592260
* {{> type }}
22602261
*/
22612262
async type(keys, delay = null) {
2263+
await checkFocusBeforeType(this)
2264+
22622265
// Always use page.keyboard.type for any string (including single character and national characters).
22632266
if (!Array.isArray(keys)) {
22642267
keys = keys.toString()

lib/helper/Puppeteer.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import promiseRetry from 'promise-retry'
88
import Locator from '../locator.js'
99
import recorder from '../recorder.js'
1010
import store from '../store.js'
11+
import { checkFocusBeforeType } from './extras/focusCheck.js'
1112
import { includes as stringIncludes } from '../assert/include.js'
1213
import { urlEquals, equals } from '../assert/equal.js'
1314
import { empty } from '../assert/empty.js'
@@ -1575,6 +1576,8 @@ class Puppeteer extends Helper {
15751576
* {{> type }}
15761577
*/
15771578
async type(keys, delay = null) {
1579+
await checkFocusBeforeType(this)
1580+
15781581
if (!Array.isArray(keys)) {
15791582
keys = keys.toString()
15801583
keys = keys.split('')

lib/helper/WebDriver.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import promiseRetry from 'promise-retry'
1010
import { includes as stringIncludes } from '../assert/include.js'
1111
import { urlEquals, equals } from '../assert/equal.js'
1212
import store from '../store.js'
13+
import { checkFocusBeforeType } from './extras/focusCheck.js'
1314
import output from '../output.js'
1415
const { debug } = output
1516
import { empty } from '../assert/empty.js'
@@ -2283,6 +2284,8 @@ class WebDriver extends Helper {
22832284
* {{> type }}
22842285
*/
22852286
async type(keys, delay = null) {
2287+
await checkFocusBeforeType(this)
2288+
22862289
if (!Array.isArray(keys)) {
22872290
keys = keys.toString()
22882291
keys = keys.split('')
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class NonFocusedType extends Error {
2+
constructor() {
3+
super(
4+
'No element is in focus. Use I.click() or I.focus() to activate an element before calling I.type(). '
5+
+ 'This error is thrown because strict mode is enabled.',
6+
)
7+
this.name = 'NonFocusedType'
8+
}
9+
}
10+
11+
export default NonFocusedType

lib/helper/extras/focusCheck.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import store from '../../store.js'
2+
import NonFocusedType from '../errors/NonFocusedType.js'
3+
4+
export async function checkFocusBeforeType(helper) {
5+
const isStrict = helper.options.strict
6+
if (!isStrict && !store.debugMode) return
7+
8+
const noFocus = await helper.executeScript(() => {
9+
const ae = document.activeElement
10+
return !ae || ae === document.documentElement || (ae === document.body && !ae.isContentEditable)
11+
})
12+
13+
if (!noFocus) return
14+
15+
if (isStrict) {
16+
throw new NonFocusedType()
17+
}
18+
19+
helper.debugSection('Warning', 'No element is in focus. Use I.click() or I.focus() to activate an element before typing.')
20+
}

test/helper/webapi.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2331,6 +2331,29 @@ export function tests() {
23312331
expect(err.message).to.include('/html')
23322332
expect(err.message).to.include('Use a more specific locator')
23332333
})
2334+
2335+
it('should throw NonFocusedType error when typing without focus', async () => {
2336+
await I.amOnPage('/form/field')
2337+
I.options.strict = true
2338+
let err
2339+
try {
2340+
await I.type('test')
2341+
} catch (e) {
2342+
err = e
2343+
}
2344+
expect(err).to.exist
2345+
expect(err.constructor.name).to.equal('NonFocusedType')
2346+
expect(err.message).to.include('No element is in focus')
2347+
expect(err.message).to.include('strict mode')
2348+
})
2349+
2350+
it('should not throw NonFocusedType when element is focused', async () => {
2351+
await I.amOnPage('/form/field')
2352+
I.options.strict = true
2353+
await I.click('Name')
2354+
await I.type('test')
2355+
await I.seeInField('Name', 'test')
2356+
})
23342357
})
23352358

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

0 commit comments

Comments
 (0)