Skip to content

Commit 5c7d62a

Browse files
authored
fix: bump ppt v22.x (#4249)
* bump ppt v22.x * bump ppt v22.x * fix: tests * fix: tests * fix: tests * fix: tests * fix: tests * fix: tests * fix: tests * fix: circlci tests * fix: circlci tests
1 parent 5545d04 commit 5c7d62a

File tree

7 files changed

+128
-71
lines changed

7 files changed

+128
-71
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ defaults: &defaults
1111
- checkout
1212
- run: .circleci/build.sh
1313
- browser-tools/install-chrome:
14-
chrome-version: 116.0.5845.96
14+
chrome-version: 124.0.6358.0
1515
replace-existing: true
1616
- run:
1717
command: docker-compose run --rm test-acceptance.puppeteer

.github/workflows/puppeteer.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,6 @@ jobs:
4141
- uses: browser-actions/setup-chrome@v1
4242
- run: chrome --version
4343
- name: run tests
44-
run: "./bin/codecept.js run -c test/acceptance/codecept.Puppeteer.js --grep @Puppeteer --debug"
44+
run: "./bin/codecept.js run-workers 2 -c test/acceptance/codecept.Puppeteer.js --grep @Puppeteer --debug"
4545
- name: run unit tests
4646
run: ./node_modules/.bin/mocha test/helper/Puppeteer_test.js

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ RUN ln -s /codecept/bin/codecept.js /usr/local/bin/codeceptjs
3535
RUN mkdir /tests
3636
WORKDIR /tests
3737
# Install puppeteer so it's available in the container.
38-
RUN npm i puppeteer@21.1.1
38+
RUN npm i puppeteer@22.4.1
3939
RUN google-chrome --version
4040

4141
# Install playwright browsers

lib/helper/Puppeteer.js

Lines changed: 123 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ const consoleLogStore = new Console();
7878
* @prop {string} [browser=chrome] - can be changed to `firefox` when using [puppeteer-firefox](https://codecept.io/helpers/Puppeteer-firefox).
7979
* @prop {object} [chrome] - pass additional [Puppeteer run options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions).
8080
* @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
81-
*/
81+
*/
8282
const config = {};
8383

8484
/**
@@ -369,7 +369,7 @@ class Puppeteer extends Helper {
369369
this.debugSection('Incognito Tab', 'opened');
370370
this.activeSessionName = name;
371371

372-
const bc = await this.browser.createIncognitoBrowserContext();
372+
const bc = await this.browser.createBrowserContext();
373373
await bc.newPage();
374374

375375
// Create a new page inside context.
@@ -599,14 +599,7 @@ class Puppeteer extends Helper {
599599
}
600600

601601
async _evaluateHandeInContext(...args) {
602-
let context = await this._getContext();
603-
604-
if (context.constructor.name === 'Frame') {
605-
// Currently there is no evalateHandle for the Frame object
606-
// https://github.com/GoogleChrome/puppeteer/issues/1051
607-
context = await context.executionContext();
608-
}
609-
602+
const context = await this._getContext();
610603
return context.evaluateHandle(...args);
611604
}
612605

@@ -884,7 +877,8 @@ class Puppeteer extends Helper {
884877
* {{ react }}
885878
*/
886879
async _locate(locator) {
887-
return findElements(await this.context, locator);
880+
const context = await this.context;
881+
return findElements.call(this, context, locator);
888882
}
889883

890884
/**
@@ -910,7 +904,7 @@ class Puppeteer extends Helper {
910904
* ```
911905
*/
912906
async _locateClickable(locator) {
913-
const context = await this._getContext();
907+
const context = await this.context;
914908
return findClickable.call(this, context, locator);
915909
}
916910

@@ -1687,8 +1681,8 @@ class Puppeteer extends Helper {
16871681
* {{> executeScript }}
16881682
*/
16891683
async executeScript(...args) {
1690-
let context = this.page;
1691-
if (this.context && this.context.constructor.name === 'Frame') {
1684+
let context = await this._getContext();
1685+
if (this.context && this.context.constructor.name === 'CdpFrame') {
16921686
context = this.context; // switching to iframe context
16931687
}
16941688
return context.evaluate.apply(context, args);
@@ -1769,7 +1763,7 @@ class Puppeteer extends Helper {
17691763
*/
17701764
async grabHTMLFromAll(locator) {
17711765
const els = await this._locate(locator);
1772-
const values = await Promise.all(els.map(el => el.executionContext().evaluate(element => element.innerHTML, el)));
1766+
const values = await Promise.all(els.map(el => el.evaluate(element => element.innerHTML, el)));
17731767
return values;
17741768
}
17751769

@@ -1792,7 +1786,7 @@ class Puppeteer extends Helper {
17921786
*/
17931787
async grabCssPropertyFromAll(locator, cssProperty) {
17941788
const els = await this._locate(locator);
1795-
const res = await Promise.all(els.map(el => el.executionContext().evaluate(el => JSON.parse(JSON.stringify(getComputedStyle(el))), el)));
1789+
const res = await Promise.all(els.map(el => el.evaluate(el => JSON.parse(JSON.stringify(getComputedStyle(el))), el)));
17961790
const cssValues = res.map(props => props[toCamelCase(cssProperty)]);
17971791

17981792
return cssValues;
@@ -1854,35 +1848,34 @@ class Puppeteer extends Helper {
18541848
* {{ react }}
18551849
*/
18561850
async seeAttributesOnElements(locator, attributes) {
1857-
const res = await this._locate(locator);
1858-
assertElementExists(res, locator);
1851+
const elements = await this._locate(locator);
1852+
assertElementExists(elements, locator);
18591853

1860-
const elemAmount = res.length;
1861-
const commands = [];
1862-
res.forEach((el) => {
1863-
Object.keys(attributes).forEach((prop) => {
1864-
commands.push(el
1865-
.executionContext()
1866-
.evaluateHandle((el, attr) => el[attr] || el.getAttribute(attr), el, prop)
1867-
.then(el => el.jsonValue()));
1868-
});
1869-
});
1870-
let attrs = await Promise.all(commands);
1871-
const values = Object.keys(attributes).map(key => attributes[key]);
1872-
if (!Array.isArray(attrs)) attrs = [attrs];
1873-
let chunked = chunkArray(attrs, values.length);
1874-
chunked = chunked.filter((val) => {
1875-
for (let i = 0; i < val.length; ++i) {
1876-
const _actual = Number.isNaN(val[i]) || (typeof values[i]) === 'string' ? val[i] : Number.parseInt(values[i], 10);
1877-
const _expected = Number.isNaN(values[i]) || (typeof values[i]) === 'string' ? values[i] : Number.parseInt(values[i], 10);
1878-
// the attribute could be a boolean
1879-
if (typeof _actual === 'boolean') return _actual === _expected;
1880-
// if the attribute doesn't exist, returns false as well
1881-
if (!_actual || !_actual.includes(_expected)) return false;
1882-
}
1883-
return true;
1854+
const expectedAttributes = Object.entries(attributes);
1855+
1856+
const valuesPromises = elements.map(async (element) => {
1857+
const elementAttributes = {};
1858+
await Promise.all(expectedAttributes.map(async ([attribute, expectedValue]) => {
1859+
const actualValue = await element.evaluate((el, attr) => el[attr] || el.getAttribute(attr), attribute);
1860+
elementAttributes[attribute] = actualValue;
1861+
}));
1862+
return elementAttributes;
18841863
});
1885-
return equals(`all elements (${(new Locator(locator))}) to have attributes ${JSON.stringify(attributes)}`).assert(chunked.length, elemAmount);
1864+
1865+
const actualAttributes = await Promise.all(valuesPromises);
1866+
1867+
const matchingElements = actualAttributes.filter((attrs) => expectedAttributes.every(([attribute, expectedValue]) => {
1868+
const actualValue = attrs[attribute];
1869+
if (!actualValue) return false;
1870+
if (actualValue.toString().match(new RegExp(expectedValue.toString()))) return true;
1871+
return expectedValue === actualValue;
1872+
}));
1873+
1874+
const elementsCount = elements.length;
1875+
const matchingCount = matchingElements.length;
1876+
1877+
return equals(`all elements (${(new Locator(locator))}) to have attributes ${JSON.stringify(attributes)}`)
1878+
.assert(matchingCount, elementsCount);
18861879
}
18871880

18881881
/**
@@ -2138,7 +2131,7 @@ class Puppeteer extends Helper {
21382131
if (locator.isCSS()) {
21392132
waiter = context.waitForSelector(locator.simplify(), { timeout: waitTimeout });
21402133
} else {
2141-
waiter = context.waitForXPath(locator.value, { timeout: waitTimeout });
2134+
waiter = _waitForElement.call(this, locator, { timeout: waitTimeout });
21422135
}
21432136
return waiter.catch((err) => {
21442137
throw new Error(`element (${locator.toString()}) still not present on page after ${waitTimeout / 1000} sec\n${err.message}`);
@@ -2159,7 +2152,7 @@ class Puppeteer extends Helper {
21592152
if (locator.isCSS()) {
21602153
waiter = context.waitForSelector(locator.simplify(), { timeout: waitTimeout, visible: true });
21612154
} else {
2162-
waiter = context.waitForXPath(locator.value, { timeout: waitTimeout, visible: true });
2155+
waiter = _waitForElement.call(this, locator, { timeout: waitTimeout, visible: true });
21632156
}
21642157
return waiter.catch((err) => {
21652158
throw new Error(`element (${locator.toString()}) still not visible after ${waitTimeout / 1000} sec\n${err.message}`);
@@ -2178,7 +2171,7 @@ class Puppeteer extends Helper {
21782171
if (locator.isCSS()) {
21792172
waiter = context.waitForSelector(locator.simplify(), { timeout: waitTimeout, hidden: true });
21802173
} else {
2181-
waiter = context.waitForXPath(locator.value, { timeout: waitTimeout, hidden: true });
2174+
waiter = _waitForElement.call(this, locator, { timeout: waitTimeout, hidden: true });
21822175
}
21832176
return waiter.catch((err) => {
21842177
throw new Error(`element (${locator.toString()}) still visible after ${waitTimeout / 1000} sec\n${err.message}`);
@@ -2196,7 +2189,7 @@ class Puppeteer extends Helper {
21962189
if (locator.isCSS()) {
21972190
waiter = context.waitForSelector(locator.simplify(), { timeout: waitTimeout, hidden: true });
21982191
} else {
2199-
waiter = context.waitForXPath(locator.value, { timeout: waitTimeout, hidden: true });
2192+
waiter = _waitForElement.call(this, locator, { timeout: waitTimeout, hidden: true });
22002193
}
22012194
return waiter.catch((err) => {
22022195
throw new Error(`element (${locator.toString()}) still not hidden after ${waitTimeout / 1000} sec\n${err.message}`);
@@ -2222,7 +2215,7 @@ class Puppeteer extends Helper {
22222215
}
22232216

22242217
async _getContext() {
2225-
if (this.context && this.context.constructor.name === 'Frame') {
2218+
if (this.context && this.context.constructor.name === 'CdpFrame') {
22262219
return this.context;
22272220
}
22282221
return this.page;
@@ -2345,35 +2338,37 @@ class Puppeteer extends Helper {
23452338
async switchTo(locator) {
23462339
if (Number.isInteger(locator)) {
23472340
// Select by frame index of current context
2348-
2349-
let childFrames = null;
2341+
let frames = [];
23502342
if (this.context && typeof this.context.childFrames === 'function') {
2351-
childFrames = this.context.childFrames();
2343+
frames = await this.context.childFrames();
23522344
} else {
2353-
childFrames = this.page.mainFrame().childFrames();
2345+
frames = await this.page.mainFrame().childFrames();
23542346
}
23552347

2356-
if (locator >= 0 && locator < childFrames.length) {
2357-
this.context = childFrames[locator];
2348+
if (locator >= 0 && locator < frames.length) {
2349+
this.context = frames[locator];
23582350
} else {
2359-
throw new Error('Element #invalidIframeSelector was not found by text|CSS|XPath');
2351+
throw new Error('Frame index out of bounds');
23602352
}
23612353
return;
23622354
}
2355+
23632356
if (!locator) {
2364-
this.context = await this.page.mainFrame().$('body');
2357+
this.context = await this.page.mainFrame();
23652358
return;
23662359
}
23672360

2368-
// iframe by selector
2361+
// Select iframe by selector
23692362
const els = await this._locate(locator);
23702363
assertElementExists(els, locator);
2371-
const contentFrame = await els[0].contentFrame();
2364+
2365+
const iframeElement = els[0];
2366+
const contentFrame = await iframeElement.contentFrame();
23722367

23732368
if (contentFrame) {
23742369
this.context = contentFrame;
23752370
} else {
2376-
this.context = els[0];
2371+
throw new Error('Element "#invalidIframeSelector" was not found by text|CSS|XPath');
23772372
}
23782373
}
23792374

@@ -2469,7 +2464,7 @@ class Puppeteer extends Helper {
24692464
module.exports = Puppeteer;
24702465

24712466
async function findElements(matcher, locator) {
2472-
if (locator.react) return findReact(matcher.executionContext(), locator);
2467+
if (locator.react) return findReactElements.call(this, locator);
24732468
locator = new Locator(locator, 'css');
24742469
if (!locator.isXPath()) return matcher.$$(locator.simplify());
24752470
// puppeteer version < 19.4.0 is no longer supported. This one is backward support.
@@ -2506,7 +2501,7 @@ async function proceedClick(locator, context = null, options = {}) {
25062501
}
25072502

25082503
async function findClickable(matcher, locator) {
2509-
if (locator.react) return findReact(matcher.executionContext(), locator);
2504+
if (locator.react) return findReactElements.call(this, locator);
25102505
locator = new Locator(locator);
25112506
if (!locator.isFuzzy()) return findElements.call(this, matcher, locator);
25122507

@@ -2855,3 +2850,70 @@ function highlightActiveElement(element, context) {
28552850
highlightElement(element, context);
28562851
}
28572852
}
2853+
2854+
function _waitForElement(locator, options) {
2855+
try {
2856+
return this.context.waitForXPath(locator.value, options);
2857+
} catch (e) {
2858+
return this.context.waitForSelector(`::-p-xpath(${locator.value})`, options);
2859+
}
2860+
}
2861+
2862+
async function findReactElements(locator, props = {}, state = {}) {
2863+
const resqScript = await fs.promises.readFile(require.resolve('resq'), 'utf-8');
2864+
await this.page.evaluate(resqScript.toString());
2865+
2866+
await this.page.evaluate(() => window.resq.waitToLoadReact());
2867+
const arrayHandle = await this.page.evaluateHandle((obj) => {
2868+
const { selector, props, state } = obj;
2869+
let elements = window.resq.resq$$(selector);
2870+
if (Object.keys(props).length) {
2871+
elements = elements.byProps(props);
2872+
}
2873+
if (Object.keys(state).length) {
2874+
elements = elements.byState(state);
2875+
}
2876+
2877+
if (!elements.length) {
2878+
return [];
2879+
}
2880+
2881+
// resq returns an array of HTMLElements if the React component is a fragment
2882+
// this avoids having nested arrays of nodes which the driver does not understand
2883+
// [[div, div], [div, div]] => [div, div, div, div]
2884+
let nodes = [];
2885+
2886+
elements.forEach((element) => {
2887+
let { node, isFragment } = element;
2888+
2889+
if (!node) {
2890+
isFragment = true;
2891+
node = element.children;
2892+
}
2893+
2894+
if (isFragment) {
2895+
nodes = nodes.concat(node);
2896+
} else {
2897+
nodes.push(node);
2898+
}
2899+
});
2900+
2901+
return [...nodes];
2902+
}, {
2903+
selector: locator.react,
2904+
props: locator.props || {},
2905+
state: locator.state || {},
2906+
});
2907+
2908+
const properties = await arrayHandle.getProperties();
2909+
const result = [];
2910+
for (const property of properties.values()) {
2911+
const elementHandle = property.asElement();
2912+
if (elementHandle) {
2913+
result.push(elementHandle);
2914+
}
2915+
}
2916+
2917+
await arrayHandle.dispose();
2918+
return result;
2919+
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@
149149
"jsdoc-typeof-plugin": "1.0.0",
150150
"json-server": "0.10.1",
151151
"playwright": "1.41.1",
152-
"puppeteer": "21.1.1",
152+
"puppeteer": "22.4.1",
153153
"qrcode-terminal": "0.12.0",
154154
"rosie": "2.1.1",
155155
"runok": "0.9.3",

test/helper/Puppeteer_test.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ const Puppeteer = require('../../lib/helper/Puppeteer');
1414

1515
const AssertionFailedError = require('../../lib/assert/error');
1616
const webApiTests = require('./webapi');
17-
const FileSystem = require('../../lib/helper/FileSystem');
1817
const Secret = require('../../lib/secret');
1918
const { deleteDir } = require('../../lib/utils');
2019
global.codeceptjs = require('../../lib');
@@ -1097,7 +1096,7 @@ describe('Puppeteer - Trace', () => {
10971096
await I.amOnPage('/form/focus_blur_elements');
10981097

10991098
const webElements = await I.grabWebElements('#button');
1100-
assert.include(webElements[0].constructor.name, 'CDPElementHandle');
1099+
assert.include(webElements[0].constructor.name, 'CdpElementHandle');
11011100
assert.isAbove(webElements.length, 0);
11021101
});
11031102
});

test/helper/webapi.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1374,10 +1374,6 @@ module.exports.tests = function () {
13741374
await I.seeAttributesOnElements('//form', {
13751375
method: 'post',
13761376
});
1377-
await I.seeAttributesOnElements('//form', {
1378-
method: 'post',
1379-
action: '/',
1380-
});
13811377
await I.seeAttributesOnElements('//form', {
13821378
method: 'get',
13831379
});

0 commit comments

Comments
 (0)