diff --git a/docs/API.md b/docs/API.md index ae6d2c01..e0fde5a9 100644 --- a/docs/API.md +++ b/docs/API.md @@ -385,7 +385,7 @@ expect(elem).toHaveId('elem') ### toHaveText -Checks if element has a specific text. +Checks if element has a specific text. Can also be called with an array as parameter in the case where the element can have different texts. ##### Usage @@ -393,11 +393,12 @@ Checks if element has a specific text. browser.url('https://webdriver.io/') const elem = $('.tagline') expect(elem).toHaveText('Next-gen browser and mobile automation test framework for Node.js') +expect(elem).toHaveText(['Next-gen browser and mobile automation test framework for Node.js', 'Adding helper functions']) ``` ### toHaveTextContaining -Checks if element contains a specific text. +Checks if element contains a specific text. Can also be called with an array as parameter in the case where the element can have different texts. ##### Usage @@ -405,6 +406,7 @@ Checks if element contains a specific text. browser.url('https://webdriver.io/') const elem = $('.tagline') expect(elem).toHaveTextContaining('browser and mobile automation test framework') +expect(elem).toHaveTextContaining(['browser and mobile automation test framework', 'helper functions']) ``` ### toBeDisplayedInViewport diff --git a/src/matchers/element/toHaveText.ts b/src/matchers/element/toHaveText.ts index e26850ac..16173c26 100644 --- a/src/matchers/element/toHaveText.ts +++ b/src/matchers/element/toHaveText.ts @@ -1,12 +1,15 @@ -import { waitUntil, enhanceError, compareText, executeCommand, wrapExpectedWithArray, updateElementsArray } from '../../utils' +import { waitUntil, enhanceError, compareText, compareTextWithArray, executeCommand, wrapExpectedWithArray, updateElementsArray } from '../../utils' import { runExpect } from '../../util/expectAdapter' -async function condition(el: WebdriverIO.Element, text: string, options: ExpectWebdriverIO.StringOptions): Promise { +async function condition(el: WebdriverIO.Element, text: string | Array, options: ExpectWebdriverIO.StringOptions): Promise { const actualText = await el.getText() + if (Array.isArray(text)) { + return compareTextWithArray(actualText, text, options) + } return compareText(actualText, text, options) } -export function toHaveTextFn(received: WebdriverIO.Element | WebdriverIO.ElementArray, text: string, options: ExpectWebdriverIO.StringOptions = {}): any { +export function toHaveTextFn(received: WebdriverIO.Element | WebdriverIO.ElementArray, text: string | Array, options: ExpectWebdriverIO.StringOptions = {}): any { const isNot = this.isNot const { expectation = 'text', verb = 'have' } = this diff --git a/src/matchers/element/toHaveTextContaining.ts b/src/matchers/element/toHaveTextContaining.ts index f889ca73..758d34a4 100644 --- a/src/matchers/element/toHaveTextContaining.ts +++ b/src/matchers/element/toHaveTextContaining.ts @@ -1,7 +1,7 @@ import { runExpect } from '../../util/expectAdapter' import { toHaveTextFn } from './toHaveText' -function toHaveTextContainingFn(el: WebdriverIO.Element, text: string, options: ExpectWebdriverIO.StringOptions = {}): any { +function toHaveTextContainingFn(el: WebdriverIO.Element, text: string | Array, options: ExpectWebdriverIO.StringOptions = {}): any { return toHaveTextFn.call(this, el, text, { ...options, containing: true diff --git a/src/types/some-expect.d.ts b/src/types/some-expect.d.ts index 62c5878d..45e8a71d 100644 --- a/src/types/some-expect.d.ts +++ b/src/types/some-expect.d.ts @@ -54,6 +54,7 @@ type WdioElementMaybePromise = declare namespace WebdriverIO { interface Element { _value: () => boolean | string; + _text: () => string; _attempts: number; } interface Browser { diff --git a/src/utils.ts b/src/utils.ts index 7c672974..7fa7c1bc 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -118,6 +118,34 @@ export const compareText = (actual: string, expected: string, { ignoreCase = fal } } +export const compareTextWithArray = (actual: string, expectedArray: Array, { ignoreCase = false, trim = false, containing = false}) => { + if (typeof actual !== 'string') { + return { + value: actual, + result: false + } + } + + if (trim) { + actual = actual.trim() + } + if (ignoreCase) { + actual = actual.toLowerCase() + expectedArray = expectedArray.map(item => item.toLowerCase()) + } + if (containing) { + const textInArray = expectedArray.some((t)=> actual.includes(t)) + return { + value: actual, + result: textInArray + } + } + return { + value: actual, + result: expectedArray.includes(actual) + } +} + function aliasFn( fn: (...args: any) => void, { verb, expectation }: { diff --git a/test/__fixtures__/wdio.js b/test/__fixtures__/wdio.js index c376b391..86da7af7 100644 --- a/test/__fixtures__/wdio.js +++ b/test/__fixtures__/wdio.js @@ -12,6 +12,10 @@ function getPropertyFn(propName) { return this._value ? this._value(propName) : undefined } +function getTextFn(propName) { + return this._text ? this._text(propName) : undefined +} + const element = { $, $$, @@ -22,7 +26,8 @@ const element = { isClickable: beFn, isFocused: beFn, isEnabled: beFn, - getProperty: getPropertyFn + getProperty: getPropertyFn, + getText: getTextFn } function $(selector, ...args) { @@ -54,8 +59,8 @@ async function waitUntil(condition, { timeout, m, interval }) { if (result) { return true } - } catch { } - attemptsLeft -- + } catch {} + attemptsLeft-- await sleep(interval) } throw new Error('waitUntil: timeout after ' + timeout) @@ -72,4 +77,4 @@ const browser = { global.browser = browser global.$ = $ -global.$$ = $$ +global.$$ = $$ \ No newline at end of file diff --git a/test/matchers/element/toHaveText.test.ts b/test/matchers/element/toHaveText.test.ts new file mode 100755 index 00000000..5839b7ba --- /dev/null +++ b/test/matchers/element/toHaveText.test.ts @@ -0,0 +1,149 @@ +import { getExpectMessage, getReceived } from '../../__fixtures__/utils'; +import { toHaveText } from '../../../src/matchers/element/toHaveText' + +describe('toHaveText', () => { + test('wait for success', async () => { + const el = await $('sel') + el._attempts = 2 + el._text= function (): string { + if (this._attempts > 0) { + this._attempts-- + return '' + } + return 'webdriverio' + } + + const result = await toHaveText(el, 'WebdriverIO', { ignoreCase: true }) + expect(result.pass).toBe(true) + expect(el._attempts).toBe(0) + }) + + test('wait but failure', async () => { + const el = await $('sel') + el._text = function (): string { + throw new Error('some error') + } + + const result = await toHaveText(el, 'WebdriverIO', { ignoreCase: true }) + expect(result.pass).toBe(false) + }) + + test('success on the first attempt', async () => { + const el = await $('sel') + el._attempts = 0 + el._text = function (): string { + this._attempts++ + return 'WebdriverIO' + } + + const result = await toHaveText(el, 'WebdriverIO', { ignoreCase: true }) + expect(result.pass).toBe(true) + expect(el._attempts).toBe(1) + }) + + test('no wait - failure', async () => { + const el = await $('sel') + el._attempts = 0 + el._text = function ():string { + this._attempts++ + return 'webdriverio' + } + + const result = await toHaveText(el, 'WebdriverIO', { wait: 0 }) + expect(result.pass).toBe(false) + expect(el._attempts).toBe(1) + }) + + test('no wait - success', async () => { + const el = await $('sel') + el._attempts = 0 + el._text = function (): string { + this._attempts++ + return 'WebdriverIO' + } + + const result = await toHaveText(el, 'WebdriverIO', { wait: 0 }) + expect(result.pass).toBe(true) + expect(el._attempts).toBe(1) + }) + + test('not - failure', async () => { + const el = await $('sel') + el._text = function (): string { + return 'WebdriverIO' + } + const result = await toHaveText.call({ isNot: true }, el, 'WebdriverIO', { wait: 0 }) + const received = getReceived(result.message()) + + expect(received).not.toContain('not') + expect(result.pass).toBe(true) + }) + + test('should return false if texts dont match', async () => { + const el = await $('sel') + el._text = function (): string { + return 'WebdriverIO' + } + + const result = await toHaveText.bind({ isNot: true })(el, 'foobar', { wait: 1 }) + expect(result.pass).toBe(false) + }) + + test('should return true if texts match', async () => { + const el = await $('sel') + el._text = function (): string { + return 'WebdriverIO' + } + + const result = await toHaveText.bind({ isNot: true })(el, 'WebdriverIO', { wait: 1 }) + expect(result.pass).toBe(true) + }) + + test('message', async () => { + const el = await $('sel') + el._text = function (): string { + return '' + } + const result = await toHaveText(el, 'WebdriverIO') + expect(getExpectMessage(result.message())).toContain('to have text') + }) + + test('success if array matches with text and ignoreCase', async () => { + const el = await $('sel') + el._attempts = 0 + el._text = function (): string { + this._attempts++ + return 'WebdriverIO' + } + + const result = await toHaveText(el, ['WDIO', 'Webdriverio'], { ignoreCase: true }) + expect(result.pass).toBe(true) + expect(el._attempts).toBe(1) + }) + + test('success if array matches with text and trim', async () => { + const el = await $('sel') + el._attempts = 0 + el._text = function (): string { + this._attempts++ + return ' WebdriverIO ' + } + + const result = await toHaveText(el, ['WDIO', 'WebdriverIO', 'toto'], { trim: true }) + expect(result.pass).toBe(true) + expect(el._attempts).toBe(1) + }) + + test('failure if array does not match with text', async () => { + const el = await $('sel') + el._attempts = 0 + el._text = function (): string { + this._attempts++ + return 'WebdriverIO' + } + + const result = await toHaveText(el, ['WDIO', 'Webdriverio'], { wait: 1 }) + expect(result.pass).toBe(false) + expect(el._attempts).toBe(1) + }) +}) diff --git a/test/utils.test.ts b/test/utils.test.ts index fd8be00e..ffab512a 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -1,4 +1,4 @@ -import { compareText } from '../src/utils' +import { compareText,compareTextWithArray } from '../src/utils' describe('utils', () => { describe('compareText', () => { @@ -22,4 +22,25 @@ describe('utils', () => { expect(compareText('qwe_AsD_zxc', 'asd', { ignoreCase: true, containing: true }).result).toBe(true) }) }) + describe('compareTextWithArray', () => { + test('string match in array', () => { + expect(compareTextWithArray('foo', ['foo', 'bar'], {}).result).toBe(true) + }) + + test('string does not match in array', () => { + expect(compareTextWithArray('foo', ['foot', 'bar'], {}).result).toBe(false) + }) + + test('trim', () => { + expect(compareTextWithArray(' foo ', ['foo', 'bar'], { trim: true }).result).toBe(true) + }) + + test('ignoreCase', () => { + expect(compareTextWithArray(' FOO ', ['foO', 'bar'], { trim: true, ignoreCase: true }).result).toBe(true) + }) + + test('containing', () => { + expect(compareTextWithArray('qwe_AsD_zxc', ['foo', 'zxc'], { ignoreCase: true, containing: true }).result).toBe(true) + }) + }) }) diff --git a/types/expect-webdriverio.d.ts b/types/expect-webdriverio.d.ts index f97eab14..417f129b 100644 --- a/types/expect-webdriverio.d.ts +++ b/types/expect-webdriverio.d.ts @@ -233,12 +233,12 @@ declare namespace ExpectWebdriverIO { /** * `WebdriverIO.Element` -> `getText` */ - toHaveText(text: string, options?: ExpectWebdriverIO.StringOptions): R + toHaveText(text: string | string[], options?: ExpectWebdriverIO.StringOptions): R /** * `WebdriverIO.Element` -> `getText` * Element's text includes the text provided */ - toHaveTextContaining(text: string, options?: ExpectWebdriverIO.StringOptions): R + toHaveTextContaining(text: string | string[], options?: ExpectWebdriverIO.StringOptions): R // ===== browser only ===== /**