Skip to content

Commit 993f019

Browse files
fix: prevent stale session and element references
Fetching the document body when setupBrowser is called means the session id and element id stored on the body element can become stale. Fetch the body element when the query is invoked to prevent this.
1 parent d472f12 commit 993f019

File tree

6 files changed

+67
-47
lines changed

6 files changed

+67
-47
lines changed

README.md

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,15 @@ npm install --save-dev webdriverio-testing-library
6060

6161
### setupBrowser
6262

63-
Accepts a WebdriverIO BrowserObject and resolves with
64-
dom-testing-library queries modifed to return WebdriverIO Elements. All the
65-
queries are async, including queryBy and getBy variants, and are bound to
66-
`document.body` by default.
63+
Accepts a WebdriverIO BrowserObject and returns dom-testing-library queries
64+
modifed to return WebdriverIO Elements. All the queries are async, including
65+
queryBy and getBy variants, and are bound to `document.body` by default.
6766

6867
```
6968
const {setupBrowser} = require('webdriverio-testing-library');
7069
7170
it('can click button', async () => {
72-
const {getByText} = await setupBrowser(browser)
71+
const {getByText} = setupBrowser(browser)
7372
7473
const button = await getByText('Button Text');
7574
await button.click();
@@ -84,13 +83,13 @@ commands are scoped to the element.
8483

8584
```
8685
it('adds queries as browser commands', async () => {
87-
await setupBrowser(browser);
86+
setupBrowser(browser);
8887
8988
expect(await browser.getByText('Page Heading')).toBeDefined()
9089
})
9190
9291
it('adds queries as element commands scoped to element', async () => {
93-
await setupBrowser(browser);
92+
setupBrowser(browser);
9493
9594
const nested = await browser.$('*[data-testid="nested"]');
9695
const button = await nested.getByText('Button Text')
@@ -132,7 +131,7 @@ afterEach(() => {
132131
})
133132
134133
it('lets you configure queries', async () => {
135-
const {getByTestId} = await setupBrowser(browser)
134+
const {getByTestId} = setupBrowser(browser)
136135
137136
expect(await getByTestId('testid-in-data-automation-id-attr')).toBeDefined()
138137
})

src/index.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -125,26 +125,35 @@ function within(element: Element) {
125125
) as WebdriverIOQueries
126126
}
127127

128-
async function setupBrowser(browser: BrowserObject | MultiRemoteBrowserObject) {
129-
const body = await browser.$('body')
130-
const queries = within(body)
128+
function setupBrowser(browser: BrowserObject | MultiRemoteBrowserObject) {
129+
const queries: { [key: string]: any } = {};
130+
131+
Object.keys(baseQueries).forEach((key) => {
132+
const queryName = key as keyof typeof baseQueries;
133+
134+
const query = async (...args: any[]) => {
135+
const body = await browser.$('body');
136+
return within(body)[queryName](...args);
137+
}
138+
139+
// add query to response queries
140+
queries[queryName] = query;
131141

132-
Object.entries(queries).forEach(([queryName, query]) => {
133142
// add query to BrowserObject
134143
browser.addCommand(queryName, query)
135144

136-
// add query to scoped to Element
145+
// add query to Elements
137146
browser.addCommand(
138147
queryName,
139148
function (...args: any[]) {
140149
const element = this as Element
141-
return within(element)[queryName as keyof typeof queries](...args)
150+
return within(element)[queryName](...args)
142151
},
143152
true,
144153
)
145154
})
146155

147-
return queries
156+
return queries as WebdriverIOQueries
148157
}
149158

150159
function configure(config: Partial<Config>) {

test/configure.e2e.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ describe('configure', () => {
99
})
1010

1111
it('supports alternative testIdAttribute', async () => {
12-
const {getByTestId} = await setupBrowser(browser)
12+
const {getByTestId} = setupBrowser(browser)
1313

1414
expect(await getByTestId('image-with-random-alt-tag')).toBeDefined()
1515
})
1616

1717
it('works after navigation', async () => {
18-
const {getByText, findByTestId} = await setupBrowser(browser)
18+
const {getByText, findByTestId} = setupBrowser(browser)
1919

2020
const goToPageTwoLink = await getByText('Go to Page 2')
2121
await goToPageTwoLink.click()

test/queries.e2e.ts

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,99 +2,99 @@ import {setupBrowser} from '../src';
22

33
describe('queries', () => {
44
it('queryBy resolves with matching element', async () => {
5-
const {queryByText} = await setupBrowser(browser)
5+
const {queryByText} = setupBrowser(browser)
66

77
const button = await queryByText('Unique Button Text')
88
expect(await button?.getText()).toEqual('Unique Button Text')
99
})
1010

1111
it('queryBy resolves with null when there are no matching elements', async () => {
12-
const {queryByText} = await setupBrowser(browser)
12+
const {queryByText} = setupBrowser(browser)
1313

1414
const button = await queryByText('Text that does not exist')
1515
expect(button).toBeNull()
1616
})
1717

1818
it('getBy resolves with matching element', async () => {
19-
const {getByText} = await setupBrowser(browser)
19+
const {getByText} = setupBrowser(browser)
2020

2121
const button = await getByText('Unique Button Text')
2222
expect(await button.getText()).toEqual('Unique Button Text')
2323
})
2424

2525
it('getBy rejects when there are no matching elements', async () => {
26-
const {getByText} = await setupBrowser(browser)
26+
const {getByText} = setupBrowser(browser)
2727

2828
await expect(getByText('Text that does not exist')).rejects.toThrow()
2929
})
3030

3131
it('getBy rejects when there are multiple matching elements', async () => {
32-
const {getByText} = await setupBrowser(browser)
32+
const {getByText} = setupBrowser(browser)
3333

3434
await expect(getByText('Button Text')).rejects.toThrow()
3535
})
3636

3737
it('findBy waits for matching element and resolves with it', async () => {
38-
const {findByText} = await setupBrowser(browser)
38+
const {findByText} = setupBrowser(browser)
3939

4040
const button = await findByText('Unique Delayed Button Text')
4141
expect(await button.getText()).toEqual('Unique Delayed Button Text')
4242
})
4343

4444
it('findBy rejects when there is no matching element after timeout', async () => {
45-
const {findByText} = await setupBrowser(browser)
45+
const {findByText} = setupBrowser(browser)
4646

4747
await expect(findByText('Text that does not exist')).rejects.toThrow()
4848
})
4949

5050
it('findBy rejects when there are multiple matching elements', async () => {
51-
const {findByText} = await setupBrowser(browser)
51+
const {findByText} = setupBrowser(browser)
5252

5353
await expect(findByText('Delayed Button Text')).rejects.toThrow()
5454
})
5555

5656
it('queryAllBy resolves with matching elements', async () => {
57-
const {queryAllByText} = await setupBrowser(browser)
57+
const {queryAllByText} = setupBrowser(browser)
5858

5959
const chans = await queryAllByText('Button Text')
6060
expect(chans).toHaveLength(2)
6161
})
6262

6363
it('queryAllBy resolves with an empty array when there are no matching elements', async () => {
64-
const {queryAllByText} = await setupBrowser(browser)
64+
const {queryAllByText} = setupBrowser(browser)
6565

6666
const chans = await queryAllByText('Text that does not exist')
6767
expect(chans).toHaveLength(0)
6868
})
6969

7070
it('getAllBy resolves matching elements', async () => {
71-
const {getAllByText} = await setupBrowser(browser)
71+
const {getAllByText} = setupBrowser(browser)
7272

7373
const buttons = await getAllByText('Button Text')
7474
expect(buttons).toHaveLength(2)
7575
})
7676

7777
it('getAllBy rejects when there are no matching elements', async () => {
78-
const {getAllByText} = await setupBrowser(browser)
78+
const {getAllByText} = setupBrowser(browser)
7979

8080
await expect(getAllByText('Text that does not exist')).rejects.toThrow()
8181
})
8282

8383
it('findAllBy waits for matching elements and resolves with them', async () => {
84-
const {findAllByText} = await setupBrowser(browser)
84+
const {findAllByText} = setupBrowser(browser)
8585

8686
const buttons = await findAllByText('Delayed Button Text')
8787
expect(buttons).toHaveLength(2)
8888
})
8989

9090
it('findAllBy rejects when there are no matching elements after timeout', async () => {
91-
const {findAllByText} = await setupBrowser(browser)
91+
const {findAllByText} = setupBrowser(browser)
9292

9393
await expect(findAllByText('Text that does not exist')).rejects.toThrow()
9494
})
9595

9696
it('can click resolved elements', async () => {
97-
const {getByText, getAllByText} = await setupBrowser(browser)
97+
const {getByText, getAllByText} = setupBrowser(browser)
9898

9999
const uniqueButton = await getByText('Unique Button Text')
100100
const buttons = await getAllByText('Button Text')
@@ -109,29 +109,29 @@ describe('queries', () => {
109109
})
110110

111111
it('support Regular Expressions', async () => {
112-
const {getAllByText} = await setupBrowser(browser)
112+
const {getAllByText} = setupBrowser(browser)
113113

114114
const chans = await getAllByText(/Jackie Chan/)
115115
expect(chans).toHaveLength(2)
116116
})
117117

118118
it('support options', async () => {
119-
const {getAllByText} = await setupBrowser(browser)
119+
const {getAllByText} = setupBrowser(browser)
120120

121121
const chans = await getAllByText('Jackie Chan', {exact: false})
122122
expect(chans).toHaveLength(2)
123123
})
124124

125125
it('support waitFor options', async () => {
126-
const {findByText} = await setupBrowser(browser)
126+
const {findByText} = setupBrowser(browser)
127127

128128
await expect(
129129
findByText('Unique Delayed Button Text', {}, {timeout: 0}),
130130
).rejects.toThrow()
131131
})
132132

133133
it('support being passed undefined arguments', async () => {
134-
const {findByText} = await setupBrowser(browser)
134+
const {findByText} = setupBrowser(browser)
135135

136136
const button = await findByText(
137137
'Unique Delayed Button Text',
@@ -142,7 +142,7 @@ describe('queries', () => {
142142
})
143143

144144
it('retains error messages', async () => {
145-
const {getByText} = await setupBrowser(browser)
145+
const {getByText} = setupBrowser(browser)
146146

147147
await expect(getByText('Text that does not exist')).rejects.toThrowError(
148148
/Unable to find an element with the text/,

test/setupBrowser.e2e.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import path from 'path'
12
import {queries as baseQueries} from '@testing-library/dom'
23

34
import {setupBrowser} from '../src'
4-
import { WebdriverIOQueries } from '../src/types'
5+
import {WebdriverIOQueries} from '../src/types'
56

67
declare global {
78
namespace WebdriverIO {
@@ -11,8 +12,8 @@ declare global {
1112
}
1213

1314
describe('setupBrowser', () => {
14-
it('resolves with all queries', async () => {
15-
const queries = await setupBrowser(browser)
15+
it('resolves with all queries', () => {
16+
const queries = setupBrowser(browser)
1617

1718
const queryNames = Object.keys(queries)
1819
Object.keys(baseQueries).forEach((queryName) =>
@@ -21,13 +22,13 @@ describe('setupBrowser', () => {
2122
})
2223

2324
it('binds queries to document body', async () => {
24-
const {getByText} = await setupBrowser(browser)
25+
const {getByText} = setupBrowser(browser)
2526

2627
expect(await getByText('Page Heading')).toBeDefined()
2728
})
2829

2930
it('still works after page navigation', async () => {
30-
const {getByText} = await setupBrowser(browser)
31+
const {getByText} = setupBrowser(browser)
3132

3233
const goToPageTwoLink = await getByText('Go to Page 2')
3334
await goToPageTwoLink.click()
@@ -36,21 +37,32 @@ describe('setupBrowser', () => {
3637
})
3738

3839
it('still works after refresh', async () => {
39-
const {getByText} = await setupBrowser(browser)
40+
const {getByText} = setupBrowser(browser)
4041

4142
await browser.refresh()
4243

4344
expect(await getByText('Page Heading')).toBeDefined()
4445
})
4546

47+
it('still works after session reload', async () => {
48+
const {getByText} = setupBrowser(browser)
49+
50+
await browser.reloadSession()
51+
await browser.url(
52+
`file:///${path.join(__dirname, '../test-app/index.html')}`,
53+
)
54+
55+
expect(await getByText('Page Heading')).toBeDefined()
56+
})
57+
4658
it('adds queries as browser commands', async () => {
47-
await setupBrowser(browser)
59+
setupBrowser(browser)
4860

4961
expect(await browser.getByText('Page Heading')).toBeDefined()
5062
})
5163

5264
it('adds queries as element commands scoped to element', async () => {
53-
await setupBrowser(browser)
65+
setupBrowser(browser)
5466

5567
const nested = await browser.$('*[data-testid="nested"]')
5668
const button = await nested.getByText('Button Text')

test/within.e2e.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ describe('within', () => {
1111
})
1212

1313
it('works with elements from GetBy query', async () => {
14-
const {getByTestId} = await setupBrowser(browser)
14+
const {getByTestId} = setupBrowser(browser)
1515
const nested = await getByTestId('nested')
1616

1717
const button = await within(nested).getByText('Button Text')
@@ -21,7 +21,7 @@ describe('within', () => {
2121
})
2222

2323
it('works with elements from AllBy query', async () => {
24-
const {getAllByTestId} = await setupBrowser(browser)
24+
const {getAllByTestId} = setupBrowser(browser)
2525

2626
const nestedDivs = await getAllByTestId(/nested/)
2727
expect(nestedDivs).toHaveLength(2)

0 commit comments

Comments
 (0)