Puppeteer: How to capture a screenshot with device frame? - puppeteer

Note: I am asking here a adapted version of this this closed question.
When using Puppeteer, it's easy to produce screenshots. It's even their first example, and it works fine on my web app:
const puppeteer = require('puppeteer');
const iPhone = puppeteer.devices['iPhone 6']; //iPhone 5/SE does not work
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.emulate(iPhone);
await page.goto('https://web.replayer.app', {
waitUntil: 'networkidle2',
});
await page.screenshot({ path: 'replayer-app.png' });
await browser.close();
})();
This produces a nice screenshot, but without device frame.
If I try to use one of the device names, that do produce a visual device frame in Google Chrome, like iPhone5/SE, I get
C:\Users\masu\Documents\node_modules\puppeteer\lib\cjs\puppeteer\common\Page.js:1500
this.setViewport(options.viewport),
^
TypeError: Cannot read properties of undefined (reading 'viewport')
at Page.emulate (C:\Users\masu\Documents\node_modules\puppeteer\lib\cjs\puppeteer\common\Page.js:1500:38)
at C:\Users\masu\Documents\puppeteer-question.js:7:14
at processTicksAndRejections (node:internal/process/task_queues:96:5)
However, since it's a web app, I would like to have the screen shot a device frame around it.
How to use and capture a screenshot from an emulated device with a viewport?

What is the cause of the issue?
I. You get this error because you are emulating the desired device with the wrong name. Even if DevTools UI names it "iPhone 5/SE" in reality DevTools Protocol only knows either "iPhone 5" or "iPhone SE" (you can give a try to give any false strings here, like "iPhone 2000", you will end up with the very same error msg). The available and valid device names are listed in DeviceDescriptors.ts on Github.
II. Since the referenced question was asked the situation is still the same: you cannot do a screenshot of the page with the device frames in puppeteer. Simply because those frames are part of the DevTools UI while Puppeteer is a high-level API to control Chrome over the DevTools Protocol: one is related to its frontend the other to its backend functionalities.
How you can proceed?
Implement the functionality in Node yourself. As I mentioned in the other post: it is easy to achieve it with node-images npm package if you have the device frames as transparent png file. E.g.: images('iPhone_5_portrait.png').draw(images('wikipedia.png').size(320), 130, 220).save('output.jpg').
Raise a feature request on Puppeteer's GitHub however it is not likely they plan to go this way (see point II. above).

Related

Stenciljs e2e test doesn't work for no apparent reason

Line within render() :
<div class={{ 'modal-backdrop show': this.show, 'modal-backdrop hide': !this.show }}>
Test :
it('should display correctly', async () => {
const page = await newE2EPage();
await page.setContent('<my-component></my-component>');
let element = await page.find('my-component');
expect(element).not.toBeNull();
element = await page.find('div.modal-backdrop.hide');
expect(element).not.toBeNull();
});
Description of the issue:
I have provided only a part of the code and unfortunately I cannot provide much more due to confidentiality. However I will do my best to describe the issue. There are two components in the project, tests for component A work as they should. Tests for component B (provided above) do not. While the first expect passes, the second one fails due to it being null but it shouldn't.
A few facts:
The project can be built, run and used without a problem.
Unit tests work as intended, including tests for the render() method.
The code in it-self is not wrong, I have tested, retested and tested again and it works for other components but not for this one.
Although the default is .hide, I have tried with both .hide and .show, neither work.
Best guess so far:
I have had many issues getting the tests to work due to how the code is written. While running tests many objects where undefined and that was causing the tests to fail. From everything that I tried and tested my best theory is that for some reason this component half fails in the context of the puppeteer browser, making the core object but nothing else. I don't know if that is possible but it looks like that.
Web components use their own document tree called shadowDOM, which isn't visible from the main DOM (page); thus your page.find fails. This concept is called encapsulation. Btw, I wasn't able to find a method called find on the page object in Puppeteer's documentation; can you explain where it comes from?
To access the shadow tree inside a web component, you'll have to access it using element.shadowRoot:
element = await page.find('my-component');
expect(element.shadowRoot.querySelector('div.modal-backdrop.hide')).not.toBeNull();
There's puppeteer addons and applications which can help with that:
https://github.com/PavelDymkov/puppeteer-shadow-selector
https://docs.puppetry.app/testing-techniques/testing-shadow-dom
To find more, check https://www.google.com/search?q=puppeteer+shadow+DOM.

element click intercepted: element is not clickable at point.. other element would receive click

Error: Element click intercepted only comes in headless chrome, it works fine if I run it on chrome browser without headless.
I have already tried below solutions:
Increasing window size in config chrome options to 'chromeOptions': { args: [ "headless","--disable-gpu", "--window-size=1920,1080" ] }
browser.actions().mouseMove(element).click(); and
browser.actions().mouseMove(element).click().perform();
Set window size in onprepare() method:
// set screen size setTimeout(function() { browser.driver.executeScript(function() { return { width: window.screen.availWidth, height: window.screen.availHeight }; }).then(function(result) { browser.driver.manage().window().setPosition(0,0); browser.driver.manage().window().setSize(result.width, result.height); }); });
Applied all types of waits: Sleep(), Implicit wait, Explicit wait, async wait: await.
Also used browser.driver.manage().window().maximize(); in before each instead of before all. But nothing of above worked. It worked for few tests but not for other.
I am running tests to check add user scenarios. Few of them are passed but not all. All tests are using same code written below only few things are changes as per test case like if it's adding a valid user, invalid user...
Basic common things in all add user script is below
Email:
Search a level on which I want to add a user:
Select Role:
click Add user button.
Problem I have observed is with search box and role dropdown. I think elements in headless chrome are not easily accessible or some other element is overlapping this elements.
Please help to solve this problem. Any help is appraised.
My tests -
it('1. should not add user with invalid email',async function(){
browser.waitForAngularEnabled(true)
browser.wait(EC.presenceOf(basepage.Loc_user_management),5000)
await basepage.Loc_user_management.click(); // click user management menu
browser.waitForAngularEnabled(true);
browser.wait(EC.presenceOf(userpage.Loc_add_user_btn),5000)
await userpage.Loc_add_user_btn.click();
browser.waitForAngularEnabled(true)
await userpage.Loc_email_addr_txtbox.click();
await userpage.Loc_email_addr_txtbox.sendKeys(loginData.users.invalid_username);
browser.manage().timeouts().implicitlyWait(1000);
browser.wait(EC.presenceOf(userpage.Loc_primarylevel_searchbox),5000);
userpage.Loc_primarylevel_searchbox.isDisplayed().then(async function(){
await userpage.Loc_primarylevel_searchbox.click();
await userpage.Loc_primarylevel_searchbox.sendKeys('Clients');
browser.actions().sendKeys(protractor.Key.ARROW_DOWN);
browser.actions().sendKeys(protractor.Key.ENTER);
//userpage.Loc_primarylevel_searchbox.click();
})
browser.sleep(500)
browser.waitForAngularEnabled(true);
browser.wait(EC.presenceOf(userpage.Loc_role_dropdown),5000)
await userpage.Loc_role_dropdown.click();
browser.waitForAngularEnabled(true)
await element(by.cssContainingText('option', 'Role1')).click();
browser.wait(EC.presenceOf(userpage.Loc_add_btn),5000)
userpage.Loc_add_btn.click().then(async function(){
expect(userpage.Loc_error_message_label.getText()).toEqual(errormessage.AddUserMessages.invalid_email_error_message);
})
});
that's LIKELY because you don't handle promises
browser.wait needs await
browser.waitForAngularEnabled also returns a promise and needs await
userpage.Loc_primarylevel_searchbox.isDisplayed().then doesn't wait until the element is displayed, it just gets a boolean if it's displayed or not and proceeds immediately. So in your case it's an extra line and complexity that doesn't do anything
sendKeys needs await
browser.sleep needs await
And don't mix .then and await. Use either one
What happens when you don't handle promises, is protractor just schedules the command, but doesn't wait until it's completed
Coincidentally, your script works with graphic interface, because chrome takes some time to load up. But when you run the same script in headless, it becomes faster (there is no UI to load) and executes one command before a previous is finished

Override default behavior of navigator.bluetooth.requestDevice()

When I call navigator.bluetooth.requestDevice({acceptAllDevices: true}), a chrome window pops up with the devices around me. I can only pick 1 device here. Is there a way to pick multiple devices or not have this window pop-up; Can I implement my own web based window that show BLE devices around me?
navigator.bluetooth.requestDevice({acceptAllDevices: true})
.then(device => {
console.log(device);
});
The Web Bluetooth GATT Communication API does not allow you to bypass this prompt. See https://developers.google.com/web/updates/2015/07/interact-with-ble-devices-on-the-web#request_bluetooth_devices
The upcoming Web Bluetooth Scanning API however will let you scan for nearby advertisements and connect to devices: https://webbluetoothcg.github.io/web-bluetooth/scanning.html
It is not fully implemented yet in Chrome.
Follow https://github.com/WebBluetoothCG/web-bluetooth/blob/master/implementation-status.md to keep track of changes.
The new navigator.bluetooth.getDevices API (in Chrome 85 and later) actually allows you to avoid this prompt IF you've previously used requestDevice to pair a device.
The chromestatus page on it is here: https://www.chromestatus.com/feature/4797798639730688
And it contains a link to a developer guide.
The simplest hackiest use would be:
navigator.bluetooth.getDevices().then(function(devices) {
if (devices.length==0) put_up_button_for_requestDevice();
else return devices[0].gatt.connect();
}).then(finish_connecting_as_normal)

Google Chrome frameless (like kiosk)

I want know if is it possible to make Google Chrome behave like a kiosk (without a frame or controls) but not in full screen, like the next mock picture:
My solution in Electron:
var app = require('app'); // Module to control application life.
var BrowserWindow = require('browser-window'); // Module to create native browser window.
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is GCed.
var mainWindow = null;
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
app.on('ready', function() {
// Create the browser window.
mainWindow = new BrowserWindow({width: 800, height: 600, frame:false});
// and load the index.html of the app.
mainWindow.loadURL('http://www.google.com/');
// Emitted when the window is closed.
mainWindow.on('closed', function() {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null;
});
});
I do not think that it's possible to make Chrome itself behave that way, but a Chrome App (not an extension) can do this.
This is an option in window creation:
chrome.app.window.create("app.html", {
frame: "none"
});
Note that you will have to provide your own controls to close/move the window.
To make it behave like a browser, you'll need to embed a <webview> element. See also a browser app example.
However, note that Chrome Apps are being deprecated.
You should consider using a similar platform, like Electron or NW.js, to build your own "mini-browser" for your purpose.

Console log for Chrome DevTools API

I am extending Google Chrome with the "chrome.devtools.panels.create" API, it means that I have now some logic in my browser by which I need to debug.
Is there anyway to see the Console log / Debug my DevTools additions?
If all you need is console.log you can wrap it up. Actually it works for any other function, not just console.log but here's example for wrapping console.log:
console._log = console.log;
console.log = function(){
// you can do whatever you want with arguments, output to div, alert / etc.
return console._log.apply(console,arguments);
};
You need to eval a script in the inspectedWindow:
chrome.devtools.inspectedWindow.eval('console.log("test")')