Skip to content

Commit fec1dd5

Browse files
(expect-webdriverio): add string options to verify text at the start, end and index and to replace the actual value (#1295)
* (expect-webdriverio): add string options to verify text at the start, end and index * (expect-webdriverio): add replace option, update docs * (expect-webdriverio): expand replacer, add option to add multiple replacers * (expect-webdriverio): update API.md * (expect-webdriverio): support object and array replacers * (expect-webdriverio): update API.md * (expect-webdriverio): change Object to Array structure
1 parent 1ec2db0 commit fec1dd5

File tree

4 files changed

+240
-20
lines changed

4 files changed

+240
-20
lines changed

docs/API.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,12 @@ This option can be applied in addition to the command options when strings are b
5050
| ---- | ---- | ------- |
5151
| <code><var>ignoreCase</var></code> | boolean | apply `toLowerCase` to both actual and expected values |
5252
| <code><var>trim</var></code> | boolean | apply `trim` to actual value |
53+
| <code><var>replace</var></code> | Replacer or Record<string, Replacer> | replace parts of the actual value that match the string/RegExp with the next string |
5354
| <code><var>containing</var></code> | boolean | expect actual value to contain expected value, otherwise strict equal. |
5455
| <code><var>asString</var></code> | boolean | might be helpful to force converting property value to string |
56+
| <code><var>atStart</var></code> | boolean | expect actual value to start with the expected value |
57+
| <code><var>atEnd</var></code> | boolean | expect actual value to end with the expected value |
58+
| <code><var>atIndex</var></code> | number | expect actual value to have the expected value at the given index |
5559

5660
##### Number Options
5761

src/utils.ts

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ const compareNumbers = (actual: number, options: ExpectWebdriverIO.NumberOptions
114114
return false
115115
}
116116

117-
export const compareText = (actual: string, expected: string | RegExp, { ignoreCase = false, trim = true, containing = false }) => {
117+
export const compareText = (actual: string, expected: string | RegExp, { ignoreCase = false, trim = true, containing = false, atStart = false, atEnd = false, atIndex, replace }: ExpectWebdriverIO.StringOptions) => {
118118
if (typeof actual !== 'string') {
119119
return {
120120
value: actual,
@@ -125,6 +125,9 @@ export const compareText = (actual: string, expected: string | RegExp, { ignoreC
125125
if (trim) {
126126
actual = actual.trim()
127127
}
128+
if (Array.isArray(replace)) {
129+
actual = replaceActual(replace, actual)
130+
}
128131
if (ignoreCase) {
129132
actual = actual.toLowerCase()
130133
if (! (expected instanceof RegExp)) {
@@ -145,13 +148,34 @@ export const compareText = (actual: string, expected: string | RegExp, { ignoreC
145148
}
146149
}
147150

151+
if (atStart) {
152+
return {
153+
value: actual,
154+
result: actual.startsWith(expected)
155+
}
156+
}
157+
158+
if (atEnd) {
159+
return {
160+
value: actual,
161+
result: actual.endsWith(expected)
162+
}
163+
}
164+
165+
if (atIndex) {
166+
return {
167+
value: actual,
168+
result: actual.substring(atIndex, actual.length).startsWith(expected)
169+
}
170+
}
171+
148172
return {
149173
value: actual,
150174
result: actual === expected
151175
}
152176
}
153177

154-
export const compareTextWithArray = (actual: string, expectedArray: Array<string | RegExp>, { ignoreCase = false, trim = false, containing = false }) => {
178+
export const compareTextWithArray = (actual: string, expectedArray: Array<string | RegExp>, { ignoreCase = false, trim = false, containing = false, atStart = false, atEnd = false, atIndex, replace }: ExpectWebdriverIO.StringOptions) => {
155179
if (typeof actual !== 'string') {
156180
return {
157181
value: actual,
@@ -162,13 +186,31 @@ export const compareTextWithArray = (actual: string, expectedArray: Array<string
162186
if (trim) {
163187
actual = actual.trim()
164188
}
189+
if (Array.isArray(replace)) {
190+
actual = replaceActual(replace, actual)
191+
}
165192
if (ignoreCase) {
166193
actual = actual.toLowerCase()
167194
expectedArray = expectedArray.map(item => (item instanceof RegExp) ? item : item.toLowerCase())
168195
}
169196

170197
const textInArray = expectedArray.some((expected) => {
171-
return expected instanceof RegExp ? !!actual.match(expected) : containing ? actual.includes(expected) : actual === expected
198+
if (expected instanceof RegExp) {
199+
return !!actual.match(expected)
200+
}
201+
if (containing) {
202+
return actual.includes(expected)
203+
}
204+
if (atStart) {
205+
return actual.startsWith(expected)
206+
}
207+
if (atEnd) {
208+
return actual.endsWith(expected)
209+
}
210+
if (atIndex) {
211+
return actual.substring(atIndex, actual.length).startsWith(expected)
212+
}
213+
return actual === expected
172214
})
173215
return {
174216
value: actual,
@@ -229,3 +271,21 @@ export {
229271
compareNumbers,
230272
aliasFn
231273
}
274+
275+
function replaceActual(replace: Replacer | Replacer[], actual: string) {
276+
const hasMultipleReplacers = (replace as Replacer[]).every(r => Array.isArray(r));
277+
const replacers = hasMultipleReplacers
278+
? replace as Replacer[]
279+
: [replace as Replacer]
280+
281+
if (replacers.some(r => Array.isArray(r) && r.length !== 2)) {
282+
throw new Error('Replacers need to have a searchValue and a replaceValue');
283+
}
284+
285+
for (const replacer of replacers) {
286+
const [searchValue, replaceValue] = replacer
287+
actual = actual.replace(searchValue, replaceValue as string)
288+
}
289+
290+
return actual
291+
}

test/matchers/element/toHaveText.test.ts

Lines changed: 111 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ describe('toHaveText', () => {
1010
test('wait for success', async () => {
1111
const el: any = await $('sel')
1212
el._attempts = 2
13-
el._text= function (): string {
13+
el._text = function (): string {
1414
if (this._attempts > 0) {
1515
this._attempts--
1616
return ''
@@ -49,7 +49,7 @@ describe('toHaveText', () => {
4949
test('no wait - failure', async () => {
5050
const el: any = await $('sel')
5151
el._attempts = 0
52-
el._text = function ():string {
52+
el._text = function (): string {
5353
this._attempts++
5454
return 'webdriverio'
5555
}
@@ -84,7 +84,7 @@ describe('toHaveText', () => {
8484
expect(result.pass).toBe(true)
8585
})
8686

87-
test('should return false if texts dont match', async () => {
87+
test('should return false if texts don\'t match', async () => {
8888
const el: any = await $('sel')
8989
el._text = function (): string {
9090
return 'WebdriverIO'
@@ -104,6 +104,66 @@ describe('toHaveText', () => {
104104
expect(result.pass).toBe(true)
105105
})
106106

107+
test('should return true if actual text + single replacer matches the expected text', async () => {
108+
const el: any = await $('sel')
109+
el._text = function (): string {
110+
return 'WebdriverIO'
111+
}
112+
113+
const result = await toHaveText.bind({})(el, 'BrowserdriverIO', { replace: ['Web', 'Browser'] })
114+
expect(result.pass).toBe(true)
115+
})
116+
117+
test('should return true if actual text + replace (string) matches the expected text', async () => {
118+
const el: any = await $('sel')
119+
el._text = function (): string {
120+
return 'WebdriverIO'
121+
}
122+
123+
const result = await toHaveText.bind({})(el, 'BrowserdriverIO', { replace: [['Web', 'Browser']] })
124+
expect(result.pass).toBe(true)
125+
})
126+
127+
test('should return true if actual text + replace (regex) matches the expected text', async () => {
128+
const el: any = await $('sel')
129+
el._text = function (): string {
130+
return 'WebdriverIO'
131+
}
132+
133+
const result = await toHaveText.bind({})(el, 'BrowserdriverIO', { replace: [[/Web/, 'Browser']] })
134+
expect(result.pass).toBe(true)
135+
})
136+
137+
test('should return true if actual text starts with expected text', async () => {
138+
const el: any = await $('sel')
139+
el._text = function (): string {
140+
return 'WebdriverIO'
141+
}
142+
143+
const result = await toHaveText.bind({})(el, 'Web', { atStart: true })
144+
expect(result.pass).toBe(true)
145+
})
146+
147+
test('should return true if actual text ends with expected text', async () => {
148+
const el: any = await $('sel')
149+
el._text = function (): string {
150+
return 'WebdriverIO'
151+
}
152+
153+
const result = await toHaveText.bind({})(el, 'IO', { atEnd: true })
154+
expect(result.pass).toBe(true)
155+
})
156+
157+
test('should return true if actual text contains the expected text at the given index', async () => {
158+
const el: any = await $('sel')
159+
el._text = function (): string {
160+
return 'WebdriverIO'
161+
}
162+
163+
const result = await toHaveText.bind({})(el, 'iverIO', { atIndex: 5 })
164+
expect(result.pass).toBe(true)
165+
})
166+
107167
test('message', async () => {
108168
const el: any = await $('sel')
109169
el._text = function (): string {
@@ -139,6 +199,50 @@ describe('toHaveText', () => {
139199
expect(el._attempts).toBe(1)
140200
})
141201

202+
test('success if array matches with text and replace (string)', async () => {
203+
const el: any = await $('sel')
204+
el._attempts = 0
205+
el._text = function (): string {
206+
this._attempts++
207+
return 'WebdriverIO'
208+
}
209+
210+
const result = await toHaveText.call({}, el, ['WDIO', 'BrowserdriverIO', 'toto'], { replace: [['Web', 'Browser']] })
211+
expect(result.pass).toBe(true)
212+
expect(el._attempts).toBe(1)
213+
})
214+
215+
test('success if array matches with text and replace (regex)', async () => {
216+
const el: any = await $('sel')
217+
el._attempts = 0
218+
el._text = function (): string {
219+
this._attempts++
220+
return 'WebdriverIO'
221+
}
222+
223+
const result = await toHaveText.call({}, el, ['WDIO', 'BrowserdriverIO', 'toto'], { replace: [[/Web/g, 'Browser']] })
224+
expect(result.pass).toBe(true)
225+
expect(el._attempts).toBe(1)
226+
})
227+
228+
test('success if array matches with text and multiple replacers and one of the replacers is a function', async () => {
229+
const el: any = await $('sel')
230+
el._attempts = 0
231+
el._text = function (): string {
232+
this._attempts++
233+
return 'WebdriverIO'
234+
}
235+
236+
const result = await toHaveText.call({}, el, ['WDIO', 'BrowserdriverIO', 'toto'], {
237+
replace: [
238+
[/Web/g, 'Browser'],
239+
[/[A-Z]g/, (match) => match.toLowerCase()],
240+
],
241+
})
242+
expect(result.pass).toBe(true)
243+
expect(el._attempts).toBe(1)
244+
})
245+
142246
test('failure if array does not match with text', async () => {
143247
const el: any = await $('sel')
144248
el._attempts = 0
@@ -158,7 +262,7 @@ describe('toHaveText', () => {
158262
beforeEach(async () => {
159263
el = await $('sel')
160264
el._text = vi.fn().mockImplementation(() => {
161-
return "This is example text"
265+
return 'This is example text'
162266
})
163267
})
164268

@@ -178,7 +282,9 @@ describe('toHaveText', () => {
178282
})
179283

180284
test('success if array matches with text and ignoreCase', async () => {
181-
const result = await toHaveText.call({}, el, ['ThIs Is ExAmPlE tExT', /Webdriver/i], { ignoreCase: true })
285+
const result = await toHaveText.call({}, el, ['ThIs Is ExAmPlE tExT', /Webdriver/i], {
286+
ignoreCase: true,
287+
})
182288
expect(result.pass).toBe(true)
183289
})
184290

0 commit comments

Comments
 (0)