Skip to content
Open
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
27 changes: 27 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,33 @@ await expect(browser).toHaveClipboardText('some clipboard text')
await expect(browser).toHaveClipboardText(expect.stringContaining('clipboard text'))
```

### toHaveLocalStorageItem

Checks if browser has a specific item in localStorage with an optional value.

##### Usage

```js
await browser.url('https://webdriver.io/')
// Check if localStorage item exists
await expect(browser).toHaveLocalStorageItem('existingKey')

// Check localStorage item with exact value
await expect(browser).toHaveLocalStorageItem('someLocalStorageKey', 'someLocalStorageValue')

// Check with case insensitive
await expect(browser).toHaveLocalStorageItem('key', 'uppercase', { ignoreCase: true })

// Check with trim
await expect(browser).toHaveLocalStorageItem('key', 'value', { trim: true })

// Check with containing
await expect(browser).toHaveLocalStorageItem('key', 'long', { containing: true })

// Check with regex
await expect(browser).toHaveLocalStorageItem('userId', /^user_\d+$/)
```

## Element Matchers

### toBeDisplayed
Expand Down
1 change: 1 addition & 0 deletions src/matchers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './matchers/browser/toHaveClipboardText.js'
export * from './matchers/browser/toHaveLocalStorageItem.js'
export * from './matchers/browser/toHaveTitle.js'
export * from './matchers/browser/toHaveUrl.js'
export * from './matchers/element/toBeClickable.js'
Expand Down
54 changes: 54 additions & 0 deletions src/matchers/browser/toHaveLocalStorageItem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { waitUntil, enhanceError, compareText } from '../../utils.js'
import { DEFAULT_OPTIONS } from '../../constants.js'

export async function toHaveLocalStorageItem(
browser: WebdriverIO.Browser,
key: string,
expectedValue?: string | RegExp | ExpectWebdriverIO.PartialMatcher,
options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS
) {
const isNot = this.isNot
const { expectation = 'localStorage item', verb = 'have' } = this

await options.beforeAssertion?.({
matcherName: 'toHaveLocalStorageItem',
expectedValue: expectedValue ? [key, expectedValue] : key,
options,
})
let actual
const pass = await waitUntil(async () => {
actual = await browser.execute((storageKey) => {
return localStorage.getItem(storageKey)
}, key)
// if no expected value is provided, we just check if the item exists
if (expectedValue === undefined) {
return actual !== null
}
// no localStorage item found
if (actual === null) {
return false
}
return compareText(actual, expectedValue, options).result
}, isNot, options)
const message = enhanceError(
'browser',
expectedValue !== undefined ? expectedValue : `localStorage item "${key}"`,
actual,
this,
verb,
expectation,
key,
options
)
const result: ExpectWebdriverIO.AssertionResult = {
pass,
message: () => message
}
await options.afterAssertion?.({
matcherName: 'toHaveLocalStorageItem',
expectedValue: expectedValue ? [key, expectedValue] : key,
options,
result
})
return result
}
1 change: 1 addition & 0 deletions test/matchers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { matchers, expect as expectLib } from '../src/index.js'
const ALL_MATCHERS = [
// browser
'toHaveClipboardText',
'toHaveLocalStorageItem',
'toHaveTitle',
'toHaveUrl',

Expand Down
147 changes: 147 additions & 0 deletions test/matchers/browser/toHaveLocalStorageItem.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { vi, expect, describe, it, beforeEach } from 'vitest'
import { browser } from '@wdio/globals'
import { toHaveLocalStorageItem } from '../../../src/matchers/browser/toHaveLocalStorageItem.js'

vi.mock('@wdio/globals')

const beforeAssertion = vi.fn()
const afterAssertion = vi.fn()

describe('toHaveLocalStorageItem', () => {
beforeEach(() => {
vi.clearAllMocks()
})

it('passes when localStorage item exists with correct value', async () => {
browser.execute = vi.fn().mockResolvedValue('someLocalStorageValue')

const result = await toHaveLocalStorageItem.call(
{}, // this context
browser,
'someLocalStorageKey',
'someLocalStorageValue',
{ ignoreCase: true, beforeAssertion, afterAssertion }
)

expect(result.pass).toBe(true)

// Check that browser.execute was called with correct arguments
expect(browser.execute).toHaveBeenCalledWith(
expect.any(Function),
'someLocalStorageKey'
)

expect(beforeAssertion).toHaveBeenCalledWith({
matcherName: 'toHaveLocalStorageItem',
expectedValue: ['someLocalStorageKey', 'someLocalStorageValue'],
options: { ignoreCase: true, beforeAssertion, afterAssertion }
})

expect(afterAssertion).toHaveBeenCalledWith({
matcherName: 'toHaveLocalStorageItem',
expectedValue: ['someLocalStorageKey', 'someLocalStorageValue'],
options: { ignoreCase: true, beforeAssertion, afterAssertion },
result
})
})

it('fails when localStorage item has different value', async () => {
browser.execute = vi.fn().mockResolvedValue('actualValue')

const result = await toHaveLocalStorageItem.call(
{},
browser,
'someKey',
'expectedValue'
)

expect(result.pass).toBe(false)
})

it('fails when localStorage item does not exist', async () => {
// Mock browser.execute to return null (item doesn't exist)
browser.execute = vi.fn().mockResolvedValue(null)

const result = await toHaveLocalStorageItem.call(
{},
browser,
'nonExistentKey',
'someValue'
)

expect(result.pass).toBe(false)
expect(browser.execute).toHaveBeenCalledWith(
expect.any(Function),
'nonExistentKey'
)
})

it('passes when only checking key existence', async () => {
// Mock browser.execute to return any non-null value
browser.execute = vi.fn().mockResolvedValue('anyValue')

const result = await toHaveLocalStorageItem.call(
{},
browser,
'existingKey'
// no expectedValue parameter
)

expect(result.pass).toBe(true)
})

it('ignores case when ignoreCase is true', async () => {
browser.execute = vi.fn().mockResolvedValue('UPPERCASE')

const result = await toHaveLocalStorageItem.call(
{},
browser,
'key',
'uppercase',
{ ignoreCase: true }
)

expect(result.pass).toBe(true)
})

it('trims whitespace when trim is true', async () => {
browser.execute = vi.fn().mockResolvedValue(' value ')

const result = await toHaveLocalStorageItem.call(
{},
browser,
'key',
'value',
{ trim: true }
)

expect(result.pass).toBe(true)
})

it('checks containing when containing is true', async () => {
browser.execute = vi.fn().mockResolvedValue('this is a long value')

const result = await toHaveLocalStorageItem.call(
{},
browser,
'key',
'long',
{ containing: true }
)

expect(result.pass).toBe(true)
})

it('passes when localStorage value matches regex', async () => {
browser.execute = vi.fn().mockResolvedValue('user_123')

const result = await toHaveLocalStorageItem.call(
{},
browser,
'userId',
/^user_\d+$/
)

expect(result.pass).toBe(true)
})
})
8 changes: 7 additions & 1 deletion types/expect-webdriverio.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,13 @@ declare namespace ExpectWebdriverIO {
*/
toHaveClipboardText(clipboardText: string | RegExp | ExpectWebdriverIO.PartialMatcher, options?: ExpectWebdriverIO.StringOptions): R

// ===== $$ only =====
/**
* `WebdriverIO.Browser` -> `execute`
* Checks if a localStorage item exists and optionally validates its value
*/
toHaveLocalStorageItem(key: string, expectedValue?: string | RegExp | ExpectWebdriverIO.PartialMatcher, options?: ExpectWebdriverIO.StringOptions): R

// ===== $ only =====
/**
* `WebdriverIO.ElementArray` -> `$$('...').length`
* supports less / greater then or equals to be passed in options
Expand Down