How to stop all JS scripts in Puppeteer - google-chrome-devtools

I would like to be able to stop any scripts from being able to run in puppeteer after the page has loaded. The reason for this is to stop carousel images and lazy loading images and essentially get the page to behave as statically as possible to enable screenshots where the images aren't changing etc.
By doing page.evaluate('debugger;') it is possible to pause the whole script, but this does not let you continue with taking screen shots as the a evaluate function does not exit until you exit the debugger (If the gui is enabled)

const page = await browser.newPage()
page.setJavaScriptEnabled(false)

If you would like to disable JavaScript after the page has loaded, you can use debugger:
await page.evaluate(() => {
debugger;
});
I was able to take screenshots after using the debugger.
Alternatively, you can replace each original node with its clone to remove the events attached to each element:
await page.evaluate(() => {
document.querySelectorAll('*').forEach(element => {
element.parentNode.replaceChild(element.cloneNode(true), element);
});
});
You can also use removeEventListener() in a loop similar to the one above to remove specific events attached to a node.
Otherwise, if you can disable JavaScript before the page has loaded, you can use page.setJavaScriptEnabled() before navigating to the page:
await page.setJavaScriptEnabled(false);

A better solution is just to block all requests with the type equals to script:
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setRequestInterception(true);
page.on("request", request => {
if (request.resourceType() === "script") {
request.abort()
} else {
request.continue()
}
})
await page.goto("https://stackoverflow.com/")
await browser.close()
})()
Source: Disabling JavaScript Using Puppeteer

If you want to freeze the page and still be able to call evaluate on it, you can
navigate to the page, wait for it to load (and maybe let its JavaScript make some DOM transformations),
get HTML snapshot of the page,
disable JavaScript,
reload the page statically (no DOM transformations will occur since JavaScript is disabled),
profit (do any amount of evaluate or screenshots on a DOM that is guaranteed to stay the same).
await page.goto('<url>', { waitUntil: 'networkidle0' }); // 1
const html = await page.content(); // 2
page.setJavaScriptEnabled(false); // 3
await page.setContent(html, { waitUntil: 'networkidle0' }); // 4

After phoning a friend the following seems to work:
await page.evaluate('document.body.innerHTML = document.body.innerHTML')

Related

is there a way to check if the PWA was launched through a file or not?

I'm using the file handle API to give my web app the optional capability to launch through double-clicking files in the file explorer.
Writing the code below, I expected if (!("files" in LaunchParams.prototype)) to check if a file was used to launch the app, but apparently, it checks if the feature is supported. OK, that makes sense.
After that, I thought the setConsumer callback would be called in any launch scenario, and files.length would be zero if the app was launched in other ways (like by typing the URL in the browser). But on those use cases, the callback was not called at all, and my init logic was never executed.
if (!("launchQueue" in window)) return textRecord.open('a welcome text');
if (!("files" in LaunchParams.prototype)) return textRecord.open('a welcome text');
launchQueue.setConsumer((launchParams) => {
if (launchParams.files.length <= 0) return textRecord.open('a welcome text');
const fileHandle = launchParams.files[0];
textRecord.open(fileHandle);
});
I've also followed the Launch Handler API article instructions and enabled the experimental API.
The new code confirms that "targetURL" in LaunchParams.prototype is true, but the setConsumer callback is not executed if the user accesses the web app through a standard browser tab.
function updateIfLaunchedByFile(textRecord) {
if (!("launchQueue" in window)) return;
if (!("files" in LaunchParams.prototype)) return;
console.log({
'"targetURL" in LaunchParams': "targetURL" in LaunchParams.prototype,
});
// this is always undefined
console.log({ "LaunchParams.targetURL": LaunchParams.targetURL });
// setConsumer does not trigger if the app is not launched by file, so it is not a good place to branch what to do in every launch situation
launchQueue.setConsumer((launchParams) => {
// this never run in a normal tab
console.log({ setConsumer: launchParams });
if (launchParams.files.length <= 0) return;
const fileHandle = launchParams.files[0];
textRecord.open(fileHandle);
});
}
This is the result...
Is there a universal way to check if the web app was launched through a file?
Check out the Launch Handler origin trial. It lets you determine the launch behavior exactly and lets your app detect how the launch happened. This API works well together with the File Handling API that you already use. You could, for example, check the LaunchParams.targetURL to see how the app was launched. Your feedback is very welcome.
Since I was not able to guarantee that the setConsumer callback was called in every situation (especially when the app is launched in a regular browser tab), I hacked it through setTimeout:
function wasFileLaunched() {
if (!("launchQueue" in window)) return;
if (!("files" in LaunchParams.prototype)) return;
return new Promise((resolve) => {
let invoked = false;
// setConsumer does not triggers if the app is not launched by file, so it is not a good place to branch what to do in every launch situation
launchQueue.setConsumer((launchParams) => {
invoked = true;
if (launchParams.files.length <= 0) return resolve();
const fileHandle = launchParams.files[0];
resolve(fileHandle);
});
setTimeout(() => {
console.log({ "setTimeout invoked =": invoked });
if (!invoked) resolve();
}, 10);
});
}

How to test toggling fullscreen on/off in protractor

I am new to industry and looking for assistance to test switching fullscreen on / off using protractor.
Like for example click to turn full screen on and click to turn full screen off.
I googled a bit and only found the following which is setting the browser to default full screen but not what I am looking for
browser.manage().window().maximize();
Appreciate any suggestions
If you are using chrome/chromium need to add in protractor config flag:
export const config = {
capabilities: {
chromeOptions: {
args: ['--start-fullscreen']
}
}
}
You may be able to achieve this functionality by getting the current size of the screen, maximizing and then comparing to your original size.
let currentSize = await browser.manage().window().getSize();
console.log('current size: ', currentSize);
await browser.manage().window().maximize();
let maximisedSize = await browser.manage().window().getSize();
console.log('max size: ', maximisedSize);
if (currentSize !== maximisedSize) console.log('Window was not maxmized')
I haven't used the .then syntax in a while but you should be able to achieve similar functionality with
browser.manage().window().getSize().then(originalSize => {
browser.manage().window().maximize().then(() => {
browser.manage().window().getSize().then(newSize => {
if(originalSize !== newSize) console.log('Window was not maxmized');
})
})
});
If there is any element on the screen to turn full screen ON, then you can simply click it or use following code:
await browser.executeScript('document.documentElement.requestFullscreen();');
And, to exit the full screen you can use document object as:
await browser.executeScript('document.exitFullscreen();');
OR
await browser.executeScript('document.webkitExitFullscreen()');

Protractor and locating element

I need to locate elemet "username" using Protractor.
I have used
browser.waitForAngularEnabled(false);
due to the fact that the login page is not Angular.
My script is as simple as this:
describe('slowchat', function() {
it('start test', function() {
browser.waitForAngularEnabled(false);
browser.get('https://www.test.no/eai/bankid20');
element(by.css('[value="Logg inn"]')).click();
browser.waitForAngularEnabled(true);
});
});
However I get error:
Failed: No element found using locator: By(css selector, [value="Logg inn"])
Please see attachment for the place I want to locate.
Any idea why this is not able to locate the button?
Now i saw that in circle is input with username but you ask about button to submit the form, btw you can turn off angular by flag
//entering non-angular site
browser.ignoreSynchronization=true;
//Some code
element(by.css('button[onclick="mySubmit()"])).click();
//when you go back to angular site
browser.ignoreSynchronization=false;
So when you disable waiting for angular or ignore synchronization, this means that you will need to roll your own waiting for some sort of stability. There are two ways you can accomplish this: 1. Turn off the control flow and use async await. 2. Resolve your promises before moving to the next action.
Resolving promises
I think you need to use the done() method call here because of the async calls. My general take away on resolving promises is ew... prefer using async / await and removing the control flow.
describe('slowchat', () => {
it('start test', (done) => {
browser.waitForAngularEnabled(false);
browser.get('https://www.test.no/eai/bankid20').then(() => {
return element(by.css('[value="Logg inn"]')).click().then() => {
browser.waitForAngularEnabled(true);
done();
});
});
});
});
Using async / await and removing the control flow
The control flow is being deprecated so this will get you ahead of the curve. I would suggest doing it this way. It will make your tests easier to debug.
describe('slowchat', () => {
it('start test', async() => {
await browser.waitForAngularEnabled(false);
await browser.get('https://www.test.no/eai/bankid20');
await element(by.css('[value="Logg inn"]')).click();
await browser.waitForAngularEnabled(true);
});
});
When launching this you will need to set the flag to turn off the control flow. In your config, you'll need to add this: SELENIUM_PROMISE_MANAGER: false

How to wait the page to test is loaded in non angular site?

I've tried this:
browser.wait(function () {
return browser.executeScript('return document.readyState==="complete" &&' +
' jQuery !== undefined && jQuery.active==0;').then(function (text) {
return text === true;
});
}, 30000);
If jQuery.active==0 then page is completely loaded. This should work for sites with JQuery and non angular pages.
However, I have many problems of instability to test for non angular sites.
How to fix this?
By default protractor waits until the page is loaded completely. If you are facing any error then it is because protractor is waiting for the default time to be completed, that you have specified in your conf.js file to wait until page loads. Change the value to wait a for longer time if you think your app is slow -
// How long to wait for a page to load.
getPageTimeout: 10000, //Increase this time to whatever you think is better
You can also increase the defaultTimeoutInterval to make protractor wait a little longer before the test fails -
jasmineNodeOpts: {
// Default time to wait in ms before a test fails.
defaultTimeoutInterval: 30000
},
If you want to wait for any particular element, then you can do so by using wait() function. Probably waiting for last element to load is the best way to test it. Here's how -
var EC = protractor.ExpectedConditions;
var lastElement = element(LOCATOR_OF_LAST_ELEMENT);
browser.wait(EC.visibilityOf(lastElement), 10000).then(function(){ //Alternatively change the visibilityOf to presenceOf to check for the element's presence only
//Perform operation on the last element
});
Hope it helps.
I use ExpectedConditions to wait for, and verify page loads. I walk through it a bit on my site, and example code on GitHub. Here's the gist...
Base Page: (gets extended by all page objects)
// wait for & verify correct page is loaded
this.at = function() {
var that = this;
return browser.wait(function() {
// call the page's pageLoaded method
return that.pageLoaded();
}, 5000);
};
// navigate to a page
this.to = function() {
browser.get(this.url, 5000);
// wait and verify we're on the expected page
return this.at();
};
...
Page Object:
var QsHomePage = function() {
this.url = 'http://qualityshepherd.com';
// pageLoaded uses Expected Conditions `and()`, that allows us to use
// any number of functions to wait for, and test we're on a given page
this.pageLoaded = this.and(
this.hasText($('h1.site-title'), 'Quality Shepherd')
...
};
QsHomePage.prototype = basePage; // extend basePage
module.exports = new QsHomePage();
The page object may contain a url (if direct access is possible), and a pageLoaded property that returns the ExepectedCondition function that we use to prove the page is loaded (and the right page).
Usage:
describe('Quality Shepherd blog', function() {
beforeEach(function() {
// go to page
qsHomePage.to();
});
it('home link should navigate home', function() {
qsHomePage.homeLink.click();
// wait and verify we're on expected page
expect(qsHomePage.at()).toBe(true);
});
});
Calling at() calls the ExpectedCondidion (which can be be an and() or an or(), etc...).
Hope this helps...

Protractor - How to obtain the new URL

I am new to Protractor (and Javascript by the way), and I am writing some tests to practice. My goal so far is to check that when I click on the home button of a website, the redirection leads me correctly to the expected address.
I have written this:
var HomeTopBanner = function() {
this.homeUrl = browser.params.homePageObject.homeUrl;
this.topBanner = element(by.css('.navbar-inner'));
this.homeButton = this.topBanner.element(by.css('.icon-home'));
}
describe('Home button', function(){
var homeTopBanner = new HomeTopBanner();
var newUrl = '';
it('clicks on the Home button', function(){
homeTopBanner.homeButton.click();
browser.getCurrentUrl().then(function storeNewUrl(url) {
newUrl = url;
});
})
it('checks that the home button leads to the homepage', function(){
expect(newUrl).toEqual(homeTopBanner.homeUrl);
})
});
This works, but my question is:
Why do I need to separate the "GetCurrentUrl" and the "expect(newUrl)" parts? I would prefer to have both of them in the same spec, but if I do that, during the comparison of the expect, newUrl=''
I assume this is related to browser.getCurrentUrl() being a promise, but is there a better way to do it?
Yes, getCurrentUrl returns a promise with the url in the form of a string as explained in the protractor api docs. You have to wait until the url is returned in order to use it. Now in order to combine both the specs you can write your expect statement inside the function that getCurrentUrl returns as shown below and there is no need of using a newUrl variable too if you want -
it('clicks on the Home button', function(){
homeTopBanner.homeButton.click();
browser.getCurrentUrl().then(function(url) {
expect(url).toEqual(homeTopBanner.homeUrl);
});
})
There could also be another issue when after the click action the previous url is being captured due to the fact that protractor is async and fast. In that case you can write your getCurrentUrl() function inside the promise that click() function returns. Here's an example of it -
it('clicks on the Home button', function(){
homeTopBanner.homeButton.click().then(function(){
browser.getCurrentUrl().then(function(url) {
expect(url).toEqual(homeTopBanner.homeUrl);
});
});
})
Hope this helps.