Skip to content

Commit 09183e8

Browse files
fix: null queries with prod React and Puppeteer (#38)
When returning queried elements directly from executeAsync Puppeteer will fail and return null if the element contains circular references. Elements in production React apps contain circular references due to React Fiber. Puppeteer is the default driver used by wdio when no services are defined. Instead of returning elements directly when Simmer fails add a data attribute with an id to use as a selector. Simmer should remain the primary method of generating selectors as it is more tolerant to scenarios where the element is regenerated, such as when React finds a key change. Co-authored-by: olivierwilkinson <[email protected]>
1 parent 50035bb commit 09183e8

File tree

1 file changed

+25
-38
lines changed

1 file changed

+25
-38
lines changed

src/index.ts

+25-38
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,9 @@ function serializeArg(arg: QueryArg): SerializedArg {
101101
}
102102

103103
type SerializedQueryResult =
104-
| {selector: string | false; element: HTMLElement}[]
104+
| {selector: string}[]
105105
| string
106-
| {selector: string | false; element: HTMLElement}
106+
| {selector: string}
107107
| null
108108

109109
function executeQuery(
@@ -154,46 +154,33 @@ function executeQuery(
154154
return done(null)
155155
}
156156

157+
function makeSelectorResult(element: HTMLElement) {
158+
// use simmer if possible to allow element refetching by position, otherwise
159+
// situations such as a React key change causes refetching to fail.
160+
const selector = window.Simmer(element)
161+
if (selector) return {selector}
162+
163+
// use generated element id as selector if Simmer fails
164+
const elementIdAttributeName = 'data-wdio-testing-lib-element-id'
165+
let elementId = element.getAttribute(elementIdAttributeName)
166+
167+
// if id doesn't already exist create one and add it to element
168+
if (!elementId) {
169+
elementId = (Math.abs(Math.random()) * 1000000000000).toFixed(0)
170+
element.setAttribute(elementIdAttributeName, elementId)
171+
}
172+
173+
return {selector:`[${elementIdAttributeName}="${elementId}"]`}
174+
}
175+
157176
if (Array.isArray(result)) {
158-
return done(
159-
result.map((element) => ({
160-
selector: window.Simmer(element),
161-
element,
162-
})),
163-
)
177+
return done(result.map(makeSelectorResult));
164178
}
165179

166-
return done({
167-
selector: window.Simmer(result),
168-
element: result,
169-
})
180+
return done(makeSelectorResult(result));
170181
})()
171182
}
172183

173-
/*
174-
Always include element key "element-6066-11e4-a52e-4f735466cecf": WebdriverIO
175-
checks whether this key is a string to determine if the selector is actually a
176-
WebElement JSON. If the selector is a WebElement JSON it uses it to create a new
177-
Element. There are valid WebElement JSONs that exclude the key but can be turned
178-
into Elements, such as { ELEMENT: elementId }; this can happen in setups that
179-
aren't generated by @wdio/cli.
180-
*/
181-
async function createElement(
182-
container: ElementBase,
183-
result: {selector: string | false; element: object},
184-
): Promise<WebdriverIO.Element> {
185-
// use selector if possible so that element can be refetched
186-
if (result.selector) {
187-
return container.$(result.selector)
188-
}
189-
190-
// fallback to using WebElement JSON if selector could not be created
191-
return container.$({
192-
'element-6066-11e4-a52e-4f735466cecf': '',
193-
...result.element,
194-
})
195-
}
196-
197184
function createQuery(container: ElementBase, queryName: QueryName) {
198185
return async (...args: QueryArg[]) => {
199186
await injectDOMTestingLibrary(container)
@@ -215,10 +202,10 @@ function createQuery(container: ElementBase, queryName: QueryName) {
215202
}
216203

217204
if (Array.isArray(result)) {
218-
return Promise.all(result.map(createElement.bind(null, container)))
205+
return Promise.all(result.map(({ selector }) => container.$(selector)))
219206
}
220207

221-
return createElement(container, result)
208+
return container.$(result.selector)
222209
}
223210
}
224211

0 commit comments

Comments
 (0)