Skip to content

Commit f50bc3e

Browse files
author
DavertMik
committed
fix(playwright): port React locator off removed _react= engine; drop Vue
Playwright 1.59 removed the experimental `_react=` and `_vue=` selector engines, which broke `{ react: ... }` and `{ vue: ... }` locators with: locator.all: TypeError: Cannot read properties of undefined (reading 'queryAll') at InjectedScript._queryEngineAll Reimplemented React locators on top of the `resq` library + `page.evaluateHandle` (the same approach the Puppeteer helper already uses). Dropped Vue locator support entirely — there is no in-deps equivalent to resq for Vue, and the public surface was minimal. - lib/helper/extras/PlaywrightReactVueLocator.js: rewrite findReact via resq; remove findVue - lib/helper/Playwright.js: remove findVue import and Vue branches in findElements / findElement - test/acceptance/react_test.js: remove the third scenario that used the raw `{ pw: '_react=...' }` form (no longer valid syntax) - test/unit/locator_test.js: drop `pw: '_react=button'` and `pw: '_vue=button'` cases - typings/index.d.ts: drop `{ vue: string }` from ILocator - docs/locators.md: update note to remove `_react`/`_vue` mentions Verified end-to-end against Playwright 1.59: React Selectors ✔ props @puppeteer @playwright (3.7s) ✔ component name @puppeteer @playwright (3.4s)
1 parent ee14063 commit f50bc3e

6 files changed

Lines changed: 47 additions & 67 deletions

File tree

docs/locators.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ I.click({ role: 'button', name: 'Submit' }, '#login-form')
2222

2323
The context narrows the search to one region of the page, and the semantic string says what the user actually clicks. This is **more precise than ARIA or CSS alone** because it combines structural scope with human-readable intent.
2424

25-
Supported strategies: `css`, `xpath`, `id`, `name`, `role`, `frame`, `shadow`, `pw`. Shadow DOM and React selectors have their own pages — see [Shadow DOM](/shadow) and [React](/react). Playwright-specific locators (`_react`, `_vue`, `data-testid`) use the `pw` strategy: `{ pw: '_react=Button[name="Save"]' }`.
25+
Supported strategies: `css`, `xpath`, `id`, `name`, `role`, `frame`, `shadow`, `pw`. Shadow DOM and React selectors have their own pages — see [Shadow DOM](/shadow) and [React](/react). Playwright-specific locators use the `pw` strategy: `{ pw: '[data-testid="save"]' }`.
2626

2727
## Locator types at a glance
2828

lib/helper/Playwright.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import MultipleElementsFound from './errors/MultipleElementsFound.js'
3636
import RemoteBrowserConnectionRefused from './errors/RemoteBrowserConnectionRefused.js'
3737
import Popup from './extras/Popup.js'
3838
import Console from './extras/Console.js'
39-
import { findReact, findVue, findByPlaywrightLocator } from './extras/PlaywrightReactVueLocator.js'
39+
import { findReact, findByPlaywrightLocator } from './extras/PlaywrightReactVueLocator.js'
4040
import { dropFile } from './scripts/dropFile.js'
4141
import WebElement from '../element/WebElement.js'
4242
import { selectElement } from './extras/elementSelection.js'
@@ -4223,13 +4223,10 @@ async function findByRole(context, locator) {
42234223
}
42244224

42254225
async function findElements(matcher, locator) {
4226-
// Check if locator is a Locator object with react/vue type, or a raw object with react/vue property
42274226
const isReactLocator = locator.type === 'react' || (locator.locator && locator.locator.react) || locator.react
4228-
const isVueLocator = locator.type === 'vue' || (locator.locator && locator.locator.vue) || locator.vue
42294227
const isPwLocator = locator.type === 'pw' || (locator.locator && locator.locator.pw) || locator.pw
42304228

42314229
if (isReactLocator) return findReact(matcher, locator)
4232-
if (isVueLocator) return findVue(matcher, locator)
42334230
if (isPwLocator) return findByPlaywrightLocator.call(this, matcher, locator)
42344231

42354232
// Handle role locators with text/exact options (e.g., {role: 'button', text: 'Submit', exact: true})
@@ -4245,7 +4242,6 @@ async function findElements(matcher, locator) {
42454242

42464243
async function findElement(matcher, locator) {
42474244
if (locator.react) return findReact(matcher, locator)
4248-
if (locator.vue) return findVue(matcher, locator)
42494245
if (locator.pw) return findByPlaywrightLocator.call(this, matcher, locator)
42504246

42514247
locator = new Locator(locator, 'css')
Lines changed: 45 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,61 @@
1+
import fs from 'fs'
2+
import { fileURLToPath } from 'url'
3+
4+
let resqScript
5+
16
async function findReact(matcher, locator) {
2-
// Handle both Locator objects and raw locator objects
37
const reactLocator = locator.locator || locator
4-
let _locator = `_react=${reactLocator.react}`;
5-
let props = '';
8+
const page = typeof matcher.page === 'function' ? matcher.page() : matcher
69

7-
if (reactLocator.props) {
8-
props += propBuilder(reactLocator.props);
9-
_locator += props;
10+
if (!resqScript) {
11+
resqScript = fs.readFileSync(fileURLToPath(import.meta.resolve('resq'))).toString()
1012
}
11-
return matcher.locator(_locator).all();
12-
}
13+
await page.evaluate(resqScript)
14+
await page.evaluate(() => window.resq.waitToLoadReact())
15+
16+
const arrayHandle = await page.evaluateHandle(
17+
({ selector, props, state }) => {
18+
let elements = window.resq.resq$$(selector)
19+
if (Object.keys(props).length) elements = elements.byProps(props)
20+
if (Object.keys(state).length) elements = elements.byState(state)
21+
if (!elements.length) return []
1322

14-
async function findVue(matcher, locator) {
15-
// Handle both Locator objects and raw locator objects
16-
const vueLocator = locator.locator || locator
17-
let _locator = `_vue=${vueLocator.vue}`;
18-
let props = '';
23+
let nodes = []
24+
elements.forEach(element => {
25+
let { node, isFragment } = element
26+
if (!node) {
27+
isFragment = true
28+
node = element.children
29+
}
30+
if (isFragment) nodes = nodes.concat(node)
31+
else nodes.push(node)
32+
})
33+
return [...nodes]
34+
},
35+
{
36+
selector: reactLocator.react,
37+
props: reactLocator.props || {},
38+
state: reactLocator.state || {},
39+
},
40+
)
1941

20-
if (vueLocator.props) {
21-
props += propBuilder(vueLocator.props);
22-
_locator += props;
42+
const properties = await arrayHandle.getProperties()
43+
await arrayHandle.dispose()
44+
const result = []
45+
for (const property of properties.values()) {
46+
const elementHandle = property.asElement()
47+
if (elementHandle) result.push(elementHandle)
2348
}
24-
return matcher.locator(_locator).all();
49+
return result
2550
}
2651

2752
async function findByPlaywrightLocator(matcher, locator) {
28-
// Handle both Locator objects and raw locator objects
2953
const pwLocator = locator.locator || locator
3054
if (pwLocator && pwLocator.toString && pwLocator.toString().includes(process.env.testIdAttribute)) {
31-
return matcher.getByTestId(pwLocator.pw.value.split('=')[1]);
55+
return matcher.getByTestId(pwLocator.pw.value.split('=')[1])
3256
}
3357
const pwValue = typeof pwLocator.pw === 'string' ? pwLocator.pw : pwLocator.pw
34-
return matcher.locator(pwValue).all();
35-
}
36-
37-
function propBuilder(props) {
38-
let _props = '';
39-
40-
for (const [key, value] of Object.entries(props)) {
41-
if (typeof value === 'object') {
42-
for (const [k, v] of Object.entries(value)) {
43-
_props += `[${key}.${k} = "${v}"]`;
44-
}
45-
} else {
46-
_props += `[${key} = "${value}"]`;
47-
}
48-
}
49-
return _props;
58+
return matcher.locator(pwValue).all()
5059
}
5160

52-
export { findReact, findVue, findByPlaywrightLocator };
61+
export { findReact, findByPlaywrightLocator }

test/acceptance/react_test.js

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,3 @@ Scenario('component name @Puppeteer @Playwright', ({ I }) => {
1919
I.seeElement({ react: 'Demo' })
2020
})
2121

22-
Scenario('using playwright locator @Playwright', ({ I }) => {
23-
I.amOnPage('https://codecept.io/test-react-calculator/')
24-
I.click('7')
25-
I.click({ pw: '_react=t[name = "="]' })
26-
I.seeElement({ pw: '_react=t[value = "7"]' })
27-
I.click({ pw: '_react=t[name = "+"]' })
28-
I.click({ pw: '_react=t[name = "3"]' })
29-
I.click({ pw: '_react=t[name = "="]' })
30-
I.seeElement({ pw: '_react=t[value = "10"]' })
31-
})

test/unit/locator_test.js

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -241,20 +241,6 @@ describe('Locator', () => {
241241
expect(l.toString()).to.equal('foo')
242242
})
243243

244-
it('should create playwright locator - _react', () => {
245-
const l = new Locator({ pw: '_react=button' })
246-
expect(l.type).to.equal('pw')
247-
expect(l.value).to.equal('_react=button')
248-
expect(l.toString()).to.equal('{pw: _react=button}')
249-
})
250-
251-
it('should create playwright locator - _vue', () => {
252-
const l = new Locator({ pw: '_vue=button' })
253-
expect(l.type).to.equal('pw')
254-
expect(l.value).to.equal('_vue=button')
255-
expect(l.toString()).to.equal('{pw: _vue=button}')
256-
})
257-
258244
it('should create playwright locator - data-testid', () => {
259245
const l = new Locator({ pw: '[data-testid="directions"]' })
260246
expect(l.type).to.equal('pw')

typings/index.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,6 @@ declare namespace CodeceptJS {
461461
| { ios: string }
462462
| { android: string; ios: string }
463463
| { react: string }
464-
| { vue: string }
465464
| { shadow: string[] }
466465
| { custom: string }
467466
| { pw: string }

0 commit comments

Comments
 (0)