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

Improve toHaveFocus #114

Merged
merged 2 commits into from
Jun 29, 2021
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
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`] = `
"expect(received).toHaveFocus(expected)
exports[`toHaveFocus element negative 1`] = `
"expect(received).toHaveFocus()

Expected: element to have focus"
Expected: true
Received: false"
`;

exports[`toHaveFocus negative: target element not found 1`] = `
"expect(received).toHaveFocus(expected)
exports[`toHaveFocus selector negative 1`] = `
"expect(received).toHaveFocus()

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

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 true"
`;
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