Skip to content

Commit e32c218

Browse files
fix: stale element refetching
When an element is stale WebdriverIO will attempt to refetch it when a command is run on it, such as a click. Since WebElement JSONs are used directly to create the WebdriverIO elements they do not have a selector which is used to attempt to refetch them. Custom locator strategies are able to add a selector to the resulting element but still fail to refetch and cause problems with within for unknown reasons. In future this may be a viable solution but not currently. Use SimmerJS to generate a unique selector for the element and send that back from the browser, then use the selector as a string selector for $. Using SimmerJS has the negative that much more text is being sent between the browser and WebdriverIO.
1 parent 38da772 commit e32c218

File tree

4 files changed

+43
-26
lines changed

4 files changed

+43
-26
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,14 @@
2828
"license": "ISC",
2929
"dependencies": {
3030
"@babel/runtime": "^7.4.3",
31-
"@testing-library/dom": "^7.0.4"
31+
"@testing-library/dom": "^7.0.4",
32+
"simmerjs": "^0.5.6"
3233
},
3334
"peerDependencies": {
3435
"webdriverio": "*"
3536
},
3637
"devDependencies": {
38+
"@types/simmerjs": "^0.5.1",
3739
"@typescript-eslint/eslint-plugin": "^4.14.0",
3840
"@typescript-eslint/parser": "^4.14.0",
3941
"@wdio/cli": "^7.3.1",

src/index.ts

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,28 @@ const DOM_TESTING_LIBRARY_UMD = fs
2929
.readFileSync(DOM_TESTING_LIBRARY_UMD_PATH)
3030
.toString()
3131

32+
const SIMMERJS = fs
33+
.readFileSync(require.resolve('simmerjs/dist/simmer.js'))
34+
.toString()
35+
3236
let _config: Partial<Config>
3337

3438
async function injectDOMTestingLibrary(container: ElementBase) {
3539
const shouldInject = await container.execute(function () {
36-
return !window.TestingLibraryDom
40+
return {
41+
domTestingLibrary: !window.TestingLibraryDom,
42+
simmer: !window.Simmer,
43+
}
3744
})
3845

39-
if (shouldInject) {
46+
if (shouldInject.domTestingLibrary) {
4047
await container.execute(DOM_TESTING_LIBRARY_UMD)
4148
}
4249

50+
if (shouldInject.simmer) {
51+
await container.execute(SIMMERJS)
52+
}
53+
4354
if (_config) {
4455
await container.execute(function (config: Config) {
4556
window.TestingLibraryDom.configure(config)
@@ -71,7 +82,7 @@ function executeQuery(
7182
container: HTMLElement,
7283
...args: any[]
7384
) {
74-
const done = args.pop() as (result: any) => void;
85+
const done = args.pop() as (result: any) => void
7586

7687
// @ts-ignore
7788
function deserializeObject(object) {
@@ -104,25 +115,20 @@ function executeQuery(
104115
waitForOptions,
105116
),
106117
)
107-
.then(done)
118+
.then((result) => {
119+
if (!result) {
120+
return done(null)
121+
}
122+
if (Array.isArray(result)) {
123+
return done(
124+
result.map((element) => ({selector: window.Simmer(element)})),
125+
)
126+
}
127+
return done({selector: window.Simmer(result)})
128+
})
108129
.catch((e) => done(e.message))
109130
}
110131

111-
/*
112-
Always include element key "element-6066-11e4-a52e-4f735466cecf": WebdriverIO
113-
checks whether this key is a string to determine if the selector is actually a
114-
WebElement JSON. If the selector is a WebElement JSON it uses it to create a new
115-
Element. There are valid WebElement JSONs that exclude the key but can be turned
116-
into Elements, such as { ELEMENT: elementId }; this can happen in setups that
117-
aren't generated by @wdio/cli.
118-
*/
119-
function createElement(container: ElementBase, elementValue: any) {
120-
return container.$({
121-
'element-6066-11e4-a52e-4f735466cecf': '',
122-
...elementValue,
123-
})
124-
}
125-
126132
function createQuery(element: ElementBase, queryName: string) {
127133
return async (...args: any[]) => {
128134
await injectDOMTestingLibrary(element)
@@ -143,10 +149,10 @@ function createQuery(element: ElementBase, queryName: string) {
143149
}
144150

145151
if (Array.isArray(result)) {
146-
return Promise.all(result.map(createElement.bind(null, element)))
152+
return Promise.all(result.map(({selector}) => element.$(selector)))
147153
}
148154

149-
return createElement(element, result)
155+
return element.$(result.selector)
150156
}
151157
}
152158

src/types.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,7 @@ export type ElementBase = {
2020
...args: any[]
2121
): Promise<T>
2222

23-
execute<T>(
24-
script: string | ((...args: any[]) => T),
25-
...args: any[]
26-
): T
23+
execute<T>(script: string | ((...args: any[]) => T), ...args: any[]): T
2724

2825
executeAsync(script: string | ((...args: any[]) => void), ...args: any[]): any
2926
}

test/async/queries.e2e.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import refetchElement from 'webdriverio/build/utils/refetchElement'
2+
13
import {setupBrowser} from '../../src'
24

35
describe('queries', () => {
@@ -157,4 +159,14 @@ describe('queries', () => {
157159
/Unable to find an element with the text/,
158160
)
159161
})
162+
163+
it('can refetch an element', async () => {
164+
const {getByText} = setupBrowser(browser)
165+
166+
const button = await getByText('Unique Button Text')
167+
168+
expect(JSON.stringify(button)).toBe(
169+
JSON.stringify(await refetchElement(button, 'click')),
170+
)
171+
})
160172
})

0 commit comments

Comments
 (0)