Skip to content

Commit ee5480b

Browse files
Merge branch 'master' into patch-1
2 parents 36dbc87 + 053431b commit ee5480b

File tree

8 files changed

+147
-53
lines changed

8 files changed

+147
-53
lines changed

.github/workflows/webdriverio-testing-library.yml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,6 @@ jobs:
1515
os: [ubuntu-latest]
1616
node: [12, 14]
1717
steps:
18-
- uses: nanasess/setup-chromedriver@master
19-
with:
20-
chromedriver-version: '79.0.3945.36'
21-
- run: |
22-
export DISPLAY=:99
23-
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & # optional
2418
- uses: actions/setup-node@v2
2519
with:
2620
node-version: ${{ matrix.node }}

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,27 +28,28 @@
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",
4042
"@wdio/local-runner": "^7.3.1",
4143
"@wdio/mocha-framework": "^7.3.1",
44+
"@wdio/selenium-standalone-service": "^7.7.3",
4245
"@wdio/spec-reporter": "^7.3.1",
4346
"@wdio/sync": "^7.3.1",
44-
"chromedriver": "^89.0.0",
4547
"eslint": "^7.6.0",
4648
"kcd-scripts": "^5.0.0",
4749
"npm-run-all": "^4.1.5",
4850
"semantic-release": "^17.0.2",
4951
"ts-node": "^9.1.1",
50-
"typescript": "^4.1.3",
51-
"wdio-chromedriver-service": "^7.0.0"
52+
"typescript": "^4.1.3"
5253
},
5354
"repository": {
5455
"type": "git",

src/index.ts

Lines changed: 85 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import path from 'path'
44
import fs from 'fs'
55
import {queries as baseQueries} from '@testing-library/dom'
6+
import 'simmerjs'
67

78
import {
89
BrowserBase,
@@ -29,15 +30,36 @@ const DOM_TESTING_LIBRARY_UMD = fs
2930
.readFileSync(DOM_TESTING_LIBRARY_UMD_PATH)
3031
.toString()
3132

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

3439
async function injectDOMTestingLibrary(container: ElementBase) {
3540
const shouldInject = await container.execute(function () {
36-
return !window.TestingLibraryDom
41+
return {
42+
domTestingLibrary: !window.TestingLibraryDom,
43+
simmer: !window.Simmer,
44+
}
3745
})
3846

39-
if (shouldInject) {
40-
await container.execute(DOM_TESTING_LIBRARY_UMD)
47+
if (shouldInject.domTestingLibrary) {
48+
await container.execute(function (library) {
49+
// add DOM Testing Library to page as a script tag to support Firefox
50+
if (navigator.userAgent.indexOf('Firefox') !== -1) {
51+
const script = document.createElement('script')
52+
script.innerHTML = library
53+
return document.head.append(script)
54+
}
55+
56+
// eval library on other browsers
57+
return eval(library)
58+
}, DOM_TESTING_LIBRARY_UMD)
59+
}
60+
61+
if (shouldInject.simmer) {
62+
await container.execute(SIMMERJS)
4163
}
4264

4365
if (_config) {
@@ -67,18 +89,19 @@ function serializeArg(arg: any) {
6789
}
6890

6991
function executeQuery(
70-
[query, container, ...args]: [QueryName, HTMLElement, ...any[]],
71-
done: (result: any) => void,
92+
query: QueryName,
93+
container: HTMLElement,
94+
...args: any[]
7295
) {
73-
// @ts-ignore
74-
function deserializeObject(object) {
96+
const done = args.pop() as (result: any) => void
97+
98+
function deserializeObject(object: object): object {
7599
return Object.entries(object)
76100
.map(([key, value]) => [key, deserializeArg(value)])
77101
.reduce((acc, [key, value]) => ({...acc, [key]: value}), {})
78102
}
79103

80-
// @ts-ignore
81-
function deserializeArg(arg) {
104+
function deserializeArg(arg: any) {
82105
if (arg && arg.RegExp) {
83106
return eval(arg.RegExp)
84107
}
@@ -93,16 +116,40 @@ function executeQuery(
93116

94117
const [matcher, options, waitForOptions] = args.map(deserializeArg)
95118

96-
Promise.resolve(
97-
window.TestingLibraryDom[query](
98-
container,
99-
matcher,
100-
options,
101-
waitForOptions,
102-
),
103-
)
104-
.then(done)
105-
.catch((e) => done(e.message))
119+
;(async () => {
120+
let result: undefined | null | HTMLElement | HTMLElement[]
121+
try {
122+
// Override RegExp to fix 'matcher instanceof RegExp' check on Firefox
123+
window.RegExp = RegExp
124+
125+
result = await window.TestingLibraryDom[query](
126+
container,
127+
matcher,
128+
options,
129+
waitForOptions,
130+
)
131+
} catch (e) {
132+
done(e.message)
133+
}
134+
135+
if (!result) {
136+
return done(null)
137+
}
138+
139+
if (Array.isArray(result)) {
140+
return done(
141+
result.map((element) => ({
142+
selector: window.Simmer(element),
143+
element,
144+
})),
145+
)
146+
}
147+
148+
return done({
149+
selector: window.Simmer(result),
150+
element: result,
151+
})
152+
})()
106153
}
107154

108155
/*
@@ -113,22 +160,32 @@ Element. There are valid WebElement JSONs that exclude the key but can be turned
113160
into Elements, such as { ELEMENT: elementId }; this can happen in setups that
114161
aren't generated by @wdio/cli.
115162
*/
116-
function createElement(container: ElementBase, elementValue: any) {
163+
function createElement(
164+
container: ElementBase,
165+
result: {selector: string | false; element: any},
166+
) {
167+
// use selector if possible so that element can be refetched
168+
if (result.selector) {
169+
return container.$(result.selector)
170+
}
171+
172+
// fallback to using WebElement JSON if selector could not be created
117173
return container.$({
118174
'element-6066-11e4-a52e-4f735466cecf': '',
119-
...elementValue,
175+
...result.element,
120176
})
121177
}
122178

123-
function createQuery(element: ElementBase, queryName: string) {
179+
function createQuery(container: ElementBase, queryName: string) {
124180
return async (...args: any[]) => {
125-
await injectDOMTestingLibrary(element)
181+
await injectDOMTestingLibrary(container)
126182

127-
const result = await element.executeAsync(executeQuery, [
183+
const result = await container.executeAsync(
184+
executeQuery,
128185
queryName,
129-
element,
186+
container,
130187
...args.map(serializeArg),
131-
])
188+
)
132189

133190
if (typeof result === 'string') {
134191
throw new Error(result)
@@ -139,10 +196,10 @@ function createQuery(element: ElementBase, queryName: string) {
139196
}
140197

141198
if (Array.isArray(result)) {
142-
return Promise.all(result.map(createElement.bind(null, element)))
199+
return Promise.all(result.map(createElement.bind(null, container)))
143200
}
144201

145-
return createElement(element, result)
202+
return createElement(container, result)
146203
}
147204
}
148205

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-app/index.html

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,27 @@ <h2>getAllByText</h2>
114114
<h2>navigate</h2>
115115
<a href="page2.html">Go to Page 2</a>
116116
</section>
117+
<section>
118+
<h2>Simmer</h2>
119+
<div>
120+
<div>
121+
<div>
122+
<div>
123+
High depth non-specific div one
124+
</div>
125+
</div>
126+
</div>
127+
</div>
128+
<div>
129+
<div>
130+
<div>
131+
<div>
132+
High depth non-specific div two
133+
</div>
134+
</div>
135+
</div>
136+
</div>
137+
</section>
117138
<!-- Prettier unindents the script tag below -->
118139
<script>
119140
document

test/async/configure.e2e.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ describe('configure', () => {
4747
await expect(() =>
4848
getByTestId('button-that-should-not-use-testid'),
4949
).rejects.toThrowError(
50-
'TestingLibraryElementError: A better query is available',
50+
'A better query is available',
5151
)
5252
})
5353

test/async/queries.e2e.ts

Lines changed: 24 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,26 @@ 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+
})
172+
173+
it('getAllBy works when Simmer cannot create a unique selector', async () => {
174+
const {getAllByText} = setupBrowser(browser)
175+
176+
expect(await getAllByText(/High depth non-specific div/)).toHaveLength(2)
177+
})
178+
179+
it('getBy works when Simmer cannot create a unique selector', async () => {
180+
const {getByText} = setupBrowser(browser)
181+
182+
expect(await getByText('High depth non-specific div one')).not.toBeNull()
183+
})
160184
})

wdio.conf.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,21 +54,21 @@ exports.config = {
5454
//
5555
capabilities: [
5656
{
57-
// maxInstances can get overwritten per capability. So if you have an in-house Selenium
58-
// grid with only 5 firefox instances available you can make sure that not more than
59-
// 5 instances get started at a time.
6057
maxInstances: 5,
61-
//
6258
browserName: 'chrome',
6359
acceptInsecureCerts: true,
64-
// If outputDir is provided WebdriverIO can capture driver session logs
65-
// it is possible to configure which logTypes to include/exclude.
66-
// excludeDriverLogs: ['*'], // pass '*' to exclude all driver session logs
67-
// excludeDriverLogs: ['bugreport', 'server'],
6860
'goog:chromeOptions': {
6961
args: process.env.CI ? ['--headless'] : [],
7062
},
7163
},
64+
{
65+
maxInstances: 5,
66+
browserName: 'firefox',
67+
acceptInsecureCerts: true,
68+
'moz:firefoxOptions': {
69+
args: process.env.CI ? ['--headless'] : [],
70+
},
71+
},
7272
],
7373
//
7474
// ===================
@@ -77,7 +77,7 @@ exports.config = {
7777
// Define all options that are relevant for the WebdriverIO instance here
7878
//
7979
// Level of logging verbosity: trace | debug | info | warn | error | silent
80-
logLevel: 'silent',
80+
logLevel: 'warn',
8181
//
8282
// Set specific log levels per logger
8383
// loggers:
@@ -117,7 +117,7 @@ exports.config = {
117117
// Services take over a specific job you don't want to take care of. They enhance
118118
// your test setup with almost no effort. Unlike plugins, they don't add new
119119
// commands. Instead, they hook themselves up into the test process.
120-
services: ['chromedriver'],
120+
services: [['selenium-standalone', {drivers: {firefox: true, chrome: true}}]],
121121

122122
// Framework you want to run your specs with.
123123
// The following are supported: Mocha, Jasmine, and Cucumber

0 commit comments

Comments
 (0)