Skip to content
This repository has been archived by the owner on Aug 20, 2024. It is now read-only.

Commit

Permalink
Improve toHaveFocus (#114)
Browse files Browse the repository at this point in the history
* Docs and types

* Update matcher and tests
  • Loading branch information
mskelton authored Jun 29, 2021
1 parent baf54f7 commit 437f3ec
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 89 deletions.
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,14 +134,19 @@ await expect(element).toBeEnabled()

### toHaveFocus

This function checks if the given selector has focus.
This function checks if a given element is focused.

You can do this via a selector on the whole page:

```js
await expect(page).toHaveFocus("#foobar")
// or via not, useful to only wait 1 second instead of for the default timeout by Playwright which is 30 seconds.
await expect(page).not.toHaveFocus("#foobar", {
timeout: 1 * 1000,
})
```

Or by passing a Playwright [ElementHandle]:

```javascript
const element = await page.$("#foobar")
await expect(element).toHaveFocus()
```

### toHaveSelector
Expand Down
6 changes: 5 additions & 1 deletion global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,16 @@ export interface PlaywrightMatchers<R> {
options?: PageWaitForSelectorOptions
): Promise<R>
/**
* Will ensure that the element has focus.
* Will check that an element on the page determined by the selector has focus.
*/
toHaveFocus(
selector: string,
options?: PageWaitForSelectorOptions
): Promise<R>
/**
* Will check that an element has focus.
*/
toHaveFocus(options?: PageWaitForSelectorOptions): Promise<R>
/**
* Will assert that N elements with the given selector are on the page and wait for it by default.
* If its 0 elements, then it will throw since the element can't be found.
Expand Down
28 changes: 12 additions & 16 deletions src/matchers/toHaveFocus/__snapshots__/index.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`toHaveFocus negative: target element don't have focus 1`] = `
"[2mexpect([22m[31mreceived[39m[2m).[22mtoHaveFocus[2m([22m[32mexpected[39m[2m)[22m
exports[`toHaveFocus element negative 1`] = `
"[2mexpect([22m[31mreceived[39m[2m).[22mtoHaveFocus[2m()[22m
Expected: element to have focus"
Expected: true
Received: false"
`;

exports[`toHaveFocus negative: target element not found 1`] = `
"[2mexpect([22m[31mreceived[39m[2m).[22mtoHaveFocus[2m([22m[32mexpected[39m[2m)[22m
exports[`toHaveFocus selector negative 1`] = `
"[2mexpect([22m[31mreceived[39m[2m).[22mtoHaveFocus[2m()[22m
Expected: element to have focus
Received: element was not found"
Expected: [32mtrue[39m
Received: [31mfalse[39m"
`;

exports[`toHaveFocus timeout should throw an error after the timeout exceeds 1`] = `
"expect(received).toHaveFocus(expected)
exports[`toHaveFocus timeout should throw an error after the timeout exceeds 1`] = `"Error: Timeout exceed for element '#foobar'"`;

Expected: element to have focus
Received: element was not found"
`;

exports[`toHaveFocus with 'not' usage negative: target element has focus 1`] = `
"expect(received).not.toHaveFocus(expected)
exports[`toHaveFocus with 'not' usage negative 1`] = `
"expect(received).not.toHaveFocus()
Expected: element to not have focus"
Expected: not [32mtrue[39m"
`;
83 changes: 60 additions & 23 deletions src/matchers/toHaveFocus/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,89 @@
import { assertSnapshot } from "../tests/utils"

const iframeSrc = `<iframe src="https://interactive-examples.mdn.mozilla.net/pages/tabbed/input-text.html">`

describe("toHaveFocus", () => {
beforeEach(async () => {
await jestPlaywright.resetContext()
})
it("positive", async () => {
await page.setContent(`<input id="foobar">`)
await page.keyboard.press("Tab")
await expect(page).toHaveFocus("#foobar")
})
it("negative: target element don't have focus", async () => {
await page.setContent(`<input id="foo"><input id="bar">`)
await page.keyboard.press("Tab")
await assertSnapshot(() => expect(page).toHaveFocus("#bar"))

describe("selector", () => {
it("positive", async () => {
await page.setContent(`<input id="foobar">`)
await page.keyboard.press("Tab")
await expect(page).toHaveFocus("#foobar")
})

it("positive in frame", async () => {
await page.setContent('<iframe src="https://example.com">')
await page.keyboard.press("Tab")

const handle = page.$("iframe")
await expect(handle).toHaveFocus("a")
await expect(await handle).toHaveFocus("a")

const frame = (await handle)?.contentFrame()
await expect(frame).toHaveFocus("a")
await expect(await frame).toHaveFocus("a")
})

it("negative", async () => {
await page.setContent(`<input id="foo"><input id="bar">`)
await page.keyboard.press("Tab")
await assertSnapshot(() => expect(page).toHaveFocus("#bar"))
})
})
it("negative: target element not found", async () => {
await page.setContent(`<input id="foo">`)
await page.keyboard.press("Tab")
await assertSnapshot(() =>
expect(page).toHaveFocus("#bar", { timeout: 1000 })
)

describe("element", () => {
it("positive", async () => {
await page.setContent(`<input id="foobar">`)
await page.keyboard.press("Tab")
const element = page.$("#foobar")
await expect(element).toHaveFocus()
await expect(await element).toHaveFocus()
})

it("negative", async () => {
await page.setContent(`<input id="foo"><input id="bar">`)
await page.keyboard.press("Tab")
await assertSnapshot(() => expect(page.$("#bar")).toHaveFocus())
})
})

describe("with 'not' usage", () => {
it("positive", async () => {
await page.setContent('<input id="foo"><input id="bar">')
await page.keyboard.press("Tab")
await expect(page).not.toHaveFocus("#bar")
})
it("negative: target element has focus", async () => {

it("positive in frame", async () => {
await page.setContent('<iframe src="https://example.com">')

const handle = page.$("iframe")
await expect(handle).not.toHaveFocus("a")
await expect(await handle).not.toHaveFocus("a")

const frame = (await handle)?.contentFrame()
await expect(frame).not.toHaveFocus("a")
await expect(await frame).not.toHaveFocus("a")
})

it("negative", async () => {
await page.setContent('<input id="foobar">')
await page.keyboard.press("Tab")
await assertSnapshot(() => expect(page).not.toHaveFocus("#foobar"))
})
})

describe("timeout", () => {
it("positive: should be able to use a custom timeout", async () => {
setTimeout(async () => {
await page.setContent(`<input id="foobar">`)
await page.keyboard.press("Tab")
}, 500)
await expect(page).toHaveFocus("#foobar", { timeout: 1000 })
})
it("should throw an error after the timeout exceeds", async () => {
const start = new Date().getTime()
await assertSnapshot(() =>
expect(page).toHaveFocus("#foobar", { timeout: 1000 })
)
const duration = new Date().getTime() - start
expect(duration).toBeGreaterThan(1000)
expect(duration).toBeLessThan(1500)
})
})
Expand Down
59 changes: 15 additions & 44 deletions src/matchers/toHaveFocus/index.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,26 @@
import { SyncExpectationResult } from "expect/build/types"
import type { Page } from "playwright-core"
import { PageWaitForSelectorOptions } from "../../../global"
import { getElementHandle, getMessage, InputArguments } from "../utils"

const enum FailureReason {
NotFound,
NotFocused,
}

async function isFocused(
page: Page,
selector: string,
options: PageWaitForSelectorOptions = {}
) {
const toHaveFocus: jest.CustomMatcher = async function (
...args: InputArguments
): Promise<SyncExpectationResult> {
try {
await page.waitForSelector(selector, options)
const [elementHandle] = await getElementHandle(args, 0)
/* istanbul ignore next */
const isFocused = await page.$eval(
selector,
const isFocused = await elementHandle.evaluate(
(el) => el === document.activeElement
)

return { pass: isFocused, reason: FailureReason.NotFocused }
} catch (e) {
return { pass: false, reason: FailureReason.NotFound }
}
}

const toHaveFocus: jest.CustomMatcher = async function (
page: Page,
selector: string,
options: PageWaitForSelectorOptions = {}
): Promise<SyncExpectationResult> {
const result = await isFocused(page, selector, options)

return {
pass: result.pass,
message: () => {
const not = this.isNot ? " not" : ""
const hint = this.utils.matcherHint("toHaveFocus", undefined, undefined, {
isNot: this.isNot,
promise: this.promise,
})

const message =
result.reason === FailureReason.NotFound
? `Expected: element to${not} have focus\n` +
"Received: element was not found"
: `Expected: element to${not} have focus`

return hint + "\n\n" + message
},
return {
pass: isFocused,
message: () => getMessage(this, "toHaveFocus", true, isFocused, ""),
}
} catch (err) {
return {
pass: false,
message: () => err.toString(),
}
}
}

Expand Down

0 comments on commit 437f3ec

Please sign in to comment.