How do I roll to a specific browser version with Playwright? - node.js

I need to run some tests using Playwright among different Chromium versions. I have different Chromium folders with different versions, but I don't know how to switch from a version to another using the CLI to run my tests. Some help? Thanks :)

You can use the executablePath argument when launching the browser to use a custom executable. See here. Note, that this only works with Chromium based browsers, see here.
const playwright = require('playwright');
(async () => {
const browser = await playwright.chromium.launch({
executablePath: '/your/custom/chromium',
headless: false, // to see the browser
slowMo: 4000 // to slow it down
});
// example for edge on msft windows
// executablePath: 'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe',
const page = await browser.newPage();
await page.goto('http://whatsmyuseragent.org/');
await page.screenshot({ path: `example.png` });
await browser.close();
})();
Also Playwright does only test against the latest stable version, so other Chromium versions might miss-behave. See here under the releases.

Max Schmitt is right: the library is not guaranteed to work with non-bundled Chromiums. Anyway, you can give it a try to multiple Chromium-based browsers in the executablePath. As it is not builtin in the Playwright Test you will need to implement it yourself.
Note: like this you lose some of the simplicity of Playwright Test.
In my example I used Jest as a test runner so yarn add --dev jest is required. The last CLI argument - reserved for the browser version - can be retrieved with process.argv.slice(-1)[0] within Node, like this you can tell your tests what browser version you want to use. Here they will be edge, chrome and the default is the bundled chromium.
MS Edge (Chromium)
yarn test chrome.test.js edge
Chrome
yarn test chrome.test.js chrome
Chromium (default - bundled with Playwright) (but any string, or the lack of this argument will also launch this as the default)
yarn test chrome.test.js chromium_default
chrome.test.js
(with Windows-specific executable paths)
const playwright = require('playwright')
let browser
let page
beforeAll(async function () {
let chromeExecutablePath
switch (process.argv.slice(-1)[0]) {
case 'chrome':
chromeExecutablePath = 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'
break
case 'edge':
chromeExecutablePath = 'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe'
break
default:
chromeExecutablePath = ''
}
browser = await playwright.chromium.launch({
headless: false,
executablePath: chromeExecutablePath
})
page = await browser.newPage()
})
describe('Google Search', function () {
test('should respond with HTTP 200 - OK', async function () {
const response = await page.goto('https://google.com')
const responseCode = response.status()
expect(responseCode).toBe(200)
})
afterAll(async function () {
await browser.close()
})
})

Related

Why does Puppeeteer cause my test suite to hang for 30 seconds when I use "waitForSelector" even though I'm calling "close" on the page and browser?

I have a Node.js Mocha test suite (I've created a minimal reproduction based on the real world application I was trying to create an automated test for).
package.json:
{
"name": "puppeteer-mocha-hang-repro",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"chai": "4.3.7",
"express": "4.18.2",
"mocha": "10.2.0",
"puppeteer": "19.6.2"
}
}
index.spec.js:
const expect = require('chai').expect;
const express = require('express');
const puppeteer = require('puppeteer');
const webServerPort = 3001;
describe('test suite', function () {
this.timeout(10000);
let webServer;
let browser;
beforeEach(async () => {
// Start web server using Express
const app = express();
app.get('/', (_, res) => {
res.send('<html>Hello, World from the <span id="source">Express web server</span>!</html>');
});
webServer = app.listen(webServerPort, () => {
console.log(`Web server listening on port ${webServerPort}.`);
});
// Start browser using Puppeteer
browser = await puppeteer.launch();
console.log('Browser launched.');
});
afterEach(async () => {
// Stop browser
await browser.close();
console.log('Browser closed.');
// Stop web server
await webServer.close();
console.log('Web server closed.');
});
it('should work', async () => {
const page = await browser.newPage();
await page.goto(`http://localhost:${webServerPort}/`);
console.log('Went to root page of web server via Puppeteer.');
if (process.env['PARSE_PAGE'] === 'true') {
const sel = await page.waitForSelector('#source');
const text = await sel.evaluate(el => el.textContent);
console.log('According to Puppeteer, the text content of the #source element on the page is:', text);
expect(text).eql('Express web server');
}
await page.close();
console.log('Page closed.');
});
});
If I run the test suite with the command npx mocha index.spec.js, which causes lines 45-48 to be skipped, the test suite passes and the Mocha process ends quickly:
$ time npx mocha index.spec.js
test suite
Web server listening on port 3001.
Browser launched.
Went to root page of web server via Puppeteer.
Page closed.
✔ should work (70ms)
Browser closed.
Web server closed.
1 passing (231ms)
real 0m0.679s
user 0m0.476s
sys 0m0.159s
Note that it finished in 0.679s.
If I instead run it with the command PARSE_PAGE=true npx mocha index.spec.js, which causes none of my code to be skipped, the tests pass quickly but the process hangs for about 30 seconds:
$ time PARSE_PAGE=true npx mocha index.spec.js
test suite
Web server listening on port 3001.
Browser launched.
Went to root page of web server via Puppeteer.
According to Puppeteer, the text content of the #source element on the page is: Express web server
Page closed.
✔ should work (79ms)
Browser closed.
Web server closed.
1 passing (236ms)
real 0m30.631s
user 0m0.582s
sys 0m0.164s
Note that it finished in 30.631s.
I suspected that this meant I was leaving things open, forgetting to call functions like close. But, I am calling close on the Express web server, Puppeteer browser, and Puppeteer page. I tried calling close on the objects I use when I don't skip any of that code, which are sel and text. But if I try that, I get error messages telling me that those objects have no such functions.
System details:
$ node --version
v18.13.0
$ npm --version
9.4.0
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 22.04.1 LTS
Release: 22.04
Codename: jammy
$ uname -r
5.10.16.3-microsoft-standard-WSL
Update: this behavior is a regression fixed by #9612 and deployed as 19.6.3. To fix the problem, upgrade to 19.6.3 (or downgrade to <= 19.6.0 if you're using an older Puppeteer for some reason).
See the original answer below.
I'm able to reproduce the hang, even without Mocha. It seems to be a bug in Puppeteer versions 19.6.1 and 19.6.2. Here's a minimal example:
const puppeteer = require("puppeteer"); // 19.6.1 or 19.6.2
const html = `<!DOCTYPE html><html><body><p>hi</p></body></html>`;
let browser;
(async () => {
browser = await puppeteer.launch();
const [page] = await browser.pages();
await page.setContent(html);
const el = await page.waitForSelector("p");
console.log(await el.evaluate(el => el.textContent));
})()
.catch(err => console.error(err))
.finally(async () => {
await browser?.close();
console.log("browser closed");
});
The culprit is page.waitForSelector, which seems to run its full 30 second default timeout even after resolving, somehow preventing the process from exiting. I've opened issue #9610 on Puppeteer's GitHub repo.
Possible workarounds:
Downgrade to 19.6.0.
Avoid using waitForSelector, since the data you want is in the static HTML (may not apply to your actual page though).
Call with page.waitForSelector("#source", {timeout: 0}) which seems to fix the problem, with the risk of stalling forever if used in a script (not a problem with mocha since the test will time out).
Call with page.waitForSelector("#source", {timeout: 1000}) which reduces the impact of the delay, with the risk of a false positive if the element takes longer than a second to load. This doesn't seem to stack, so if you use a 1-3 second delay across many tests, mocha should exit within a few seconds of all tests completing rather than the sum of all delays across all waitForSelector calls. This isn't practical in most scripts, though.
Run npx mocha --exit index.spec.js. Not recommended--this suppresses the issue.
I'm not sure if the behavior is specific to waitForTimeout or if it may apply to other waitFor-family methods.
As an aside, your server listen and close calls are technically race conditions, so:
await new Promise(resolve =>
webServer = app.listen(webServerPort, () => {
console.log(`Web server listening on port ${webServerPort}.`);
resolve();
})
);
and
await new Promise(resolve => webServer.close(() => resolve()));
System details:
$ node --version
v18.7.0
$ npm --version
9.3.0
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 22.04.1 LTS
Release: 22.04
Codename: jammy
$ uname -r
5.15.0-56-generic
I also confirmed the behavior on Windows 10.
I am not sure how much it will be helpful but you can try this:
if (process.env['PARSE_PAGE'] === 'true') {
const sel = await page.waitForSelector('#source');
const text = await page.evaluate(el => el.textContent, sel);
console.log('According to Puppeteer, the text content of the #source element on the page is:', text);
expect(text).eql('Express web server');
}
Also, check for global hooks!

Set puppeteer window size when running not headless, but via console

I was trying every solution I found, but couldn't set my Chromium window size (nor viewport) to any other than 800x600. I am running it on Debian server via SSH. I know there are similar questions with answers already, but these answers aren't working for me at all.
These are 2 options that I found combined:
const puppeteer = require('puppeteer');
(async() => {
const browser = await puppeteer.launch({args: ['--no-sandbox','--window-size=1920,1080']});
const page = await browser.newPage();
await page._client.send('Emulation.clearDeviceMetricsOverride');
await page.setViewport({
width: 1920,
height: 1080
})
await page.goto('https://example.com',{waitUntil: 'networkidle2'});
await page.screenshot({path: 'test.png'});
browser.close();
})();
But it simply doesn't work. It does in --headless mode, but not in normal mode.
The screenshot is always 800x600, even when I set the numbers to lower than that. Any ideas why? Please note that the system is a server one, without X11.
Check the documentation on dependencies for your operating system:
https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#chrome-headless-doesnt-launch-on-unix
Install the requirements listed there and try again.

Global in jest puppeteer custom test environment loses context in test

I'm writing a npm module to reduce the boiler code in my test project and this module will be added as a dev dependency in that test project. I'm using prototype inheritance to extend an existing class(Page) of puppeteer library as well in this npm module.
When I create a global browser in custom test environment and use it to create page instance in the test. The Page class loses reference to all properties I have added to it using prototypal inheritance. And it throws error page.sendText is not a function
But when I create the browser instance in the test. I am able to use the properties I have added to Page class.
Creating global.browser in custom test environment.
Creating browser instance in test.
GLOBALSETUP in npm module
const browser = await puppeteer.launch(await puppeteer.launch({some config});
TEST ENVIRONMENT in npm module:
this.global __BROWSER__ = await puppeteer.connect({
browserWSEndpoint: wsEndpoint,
});
Extended PAGE class in npm module:
const Page = require('puppeteer/lib/Page').Page;
Page.prototype.sendText = async (selector, text) => {
let element = await this.waitForSelector(selector, {visible : true});
await element.type(text);
}
Test in test project after locally installing the npm module:
describe('awesome test', () => {
it('something will be ok', async () => {
const context = global.__BROWSER__.defaultBrowserContext();
page = await context.newPage();
await page.goto('https://google.com/')
await page.sendText('#search', "puppeteer")
await page.screenshot({path: 'google.png', fullPage: true})
})
});
The error is -> page.sendText is not a function
I was expecting the sendText would work.

Different browser behavior when pass .env variables in command for run tests

It's not actually a problem, but I do not fully understand, what happened and why.
I have this runner for my test. I test a React app.
let testcafe = null
const isCiEnv = process.env.CI === 'true'
const exit = async err => {
console.log('Exiting...')
if (testcafe) {
console.log('Closing TestCafe...')
testcafe.close()
}
console.log('Exiting process...')
process.exit(err ? 1 : 0)
}
console.log('Is CI ENV: ', isCiEnv)
console.log('Creating TestCafe...')
createTestCafe('localhost', 1337, 1338)
.then(tc => {
testcafe = tc
})
.then(() => {
console.log('Starting server...')
return startServer()
})
.then(() => {
console.log('Starting client...')
return startClient()
})
.then(() => {
console.log('Creating TestCafe Runner...')
return testcafe.createRunner()
})
.then(runner => {
console.log('About to start TestCafe Runner...')
return runner
.src([
'test/e2e/fixtures/auth.js'
])
.browsers({
path: isCiEnv
? '/usr/bin/chromium-browser'
: 'Chrome',
cmd: isCiEnv
? '--no-sandbox --disable-gpu'
: undefined
})
.screenshots('screenshots', true)
.run({
skipJsErrors: true,
selectorTimeout: 25000,
assertionTimeout: 25000
})
})
.then(failedCount => {
console.log('failed count:', failedCount)
return exit(failedCount)
})
.catch(err => {
console.error('ERR', err)
return exit(err)
})
In package.json i have this command for run test
"test:e2e": "HOST=0.0.0.0 NODE_ENV=test NODE_PATH=server babel-node test/e2e/index.js --presets stage-2"
But in the local environment, I run a test with this command
sudo REDIS_HOST=127.0.0.1 PORT=80 yarn test:e2e
That because on my local machine I have different config and I don't want to change it for everyone else.
Usually, test runs in a different, clear version of the browser, without any account data, plugins and etc. But in this case, tests run in a new browser window, but with all plugins and my account name. But, it's doesn't have cookie and session auth data from the browser window, in which I usually work (because I authorized on-site in the working browser and doesn't auth in test browser).
And if I change "Chrome" to "chrome" it stops run completely. Same behavior for Firefox and Safari.
Earlier, without passing REDIS_HOST and HOST, it works as usual and runs in a clean new browser window.
It's not a big problem, for now at least, but it's unexpected behavior and I don't understand, why it works this way.
I'm not very familiar with Node and React, and maybe this related to them.
Spec: macOS 10.12.5, Testcafe 0.20.3, Chrome 67
Specifying browsers using { path, cmd } is a legacy low-level option, you shouldn't use it. When a browser is specified in this way, TestCafe doesn't try to guess browser's type (Chrome, Firefox) and doesn't perform advanced initialization steps like creating a clean profile (because profile structure depends on browser's type). So it's better to use the following runner code:
.browsers(isCiEnv ? 'chromium --no-sandbox --disable-gpu' : 'chrome')

How to use Node.js without .js files, callbacks, promises, etc?

I'm trying to run node without .js files to debug some automation I want to do with chrome headless (puppeteer).
I want to type 1 command, then see the result, then type another one, etc. Here's the problem I come into:
node --require puppeteer
> var puppeteer = require('puppeteer')
> var browser = puppeteer.launch({headless: false, waitUntil: 'networkidle'})
So far so good, browser opens.
> var page = browser.newPage()
Then I get this error TypeError: browser.newPage is not a function
Now I know that puppeteer.launch() returns a promise, which is why it's not working. But how can I use it without writing a promise chain in a .js file? I want to type one command, see the result, type another one, etc. I don't want to have to reopen the browser every time.
You could do something like this:
node --require puppeteer
> var puppeteer = require('puppeteer');
> var browser = null;
> puppeteer.launch({headless: false, waitUntil: 'networkidle'})
.then(tempBrowser => browser = tempBrowser);
> var page = browser.newPage();
...

Resources