How do I deal with a page loading in a secondary browser tab when I run Protractor tests headless (Chrome)? - selenium

When this code clicks a “Print”-icon, a PDF-file will be generated and displayed in a new browser-tab. I want to switch to this tab, wait until the PDF has finished loading there, check a part of the URL and then close that secondary tab.
it( "should open document as PDF-file in new browser-tab", async () => {
const mUrl = "TherapyReportForm/Export";
await mTherapyReportView.btnPrintform.click();
await browser.getAllWindowHandles().then(async (handles) => {
//if there is a secondary browser-tab open...
if (handles.length > 1) {
//...click on it
await browser.driver.switchTo().window(handles[1]);
}
});
//confirm that the url of the secondary tab matches the print-url pattern
await browser.sleep( 18000 );
expect( await browser.getCurrentUrl() ).toContain( mUrl );
await browser.getAllWindowHandles().then( async (handles) => {
//if there are multiple browser-tabs open
if (handles.length > 1) {
//close the secondary and move back to first
await browser.driver.close();
await browser.driver.switchTo().window( handles[0] );
}
} );
} );
The test above works reliably, unless I run it in chromes headless-mode, then the test-run breaks at line
expect(await browser.getCurrentUrl()).toContain(mUrl);
console output
The console output proves that it switches to the secondary tab, but apparently never tries to load the url. Since it fails to close this secondary tab the entire suite will break at that point.
What am I doing wrong?

Here is a thing... downloading functionality is not available in headless chrome. That's for sure. What I'm going to talk about below, I'm a little bit uncertain if that's the case
There is no such thing as 'open' pdf file in browser. The reason is that behind scene the browser actually downloads it (maybe temporarily). This is why you'll never be able to do that in headless
But that's rather a shot in the dark

Related

How to use Puppetter with express API and properly closing the browser without affecting other concurrent request

I have a website which has some data as HTML (loaded via ajax) and I have to convert that to JSON with custom formatting.
So, for this I choose Puppeteer.
const browser = await puppeteer.launch({
headless: true, args: ['--no-sandbox']
});
const page = await browser.newPage();
This web API that I'm developing will be having concurrent web requests, so I thought browser.close() might affect the other concurrent requests, so I decide to do only page.close.
One problem that I'm facing is when I do puppeteer.launch, for each request it opens two about:blank tab in a new window.
And when browser.newPage() is requested it returns one of the blank tabs and leaves the other one opened.
That leads to multiple window opened with multiple about:blank.
Here I don't know the right way to handle this, I can't close the browser because it will close all the pages which are being used by other requests.
You are seeing an empty (about:blank) tab each time you run this code, because you are doing two things here:
Launching a new browser - which already starts with an open tab
const browser = await puppeteer.launch({
headless: true, args: ['--no-sandbox']
});
Opening a new tab.
const page = await browser.newPage();
If you don't want to have "zombie" blank tabs, then you can just reuse the initial tab like this:
const browser = await puppeteer.launch({
headless: true, args: ['--no-sandbox']
});
const currentPages = await browser.pages(); // list the opened tabs
const [page] = currentPages; // use the first (and only) opened tab.
Note that in this case, since you are just reusing the only one and initial tab, closing it with page.close() will have the same effect as closing the browser with browser.close().
Exploring some Express + Browser concurrency alternatives
Consider that a different solution would take place if you want to reuse the same browser instance for the lifetime of your Express server, ie. serve all requests on the same browser, or if you want to use a new browser instance for each individual request.
1. One browser instance per server
In this case it might make sense, depending on your requirements, in managing one tab per request.
// launch the browser instance, once
const browser = await puppeteer.launch({
headless: true, args: ['--no-sandbox']
});
// handle incoming requests
app.get("/foo", async (req, res) => {
const page = await browser.newPage();
try {
// ... execute some logic on this new page
} catch(error) {
// whoops, logic went wrong, respond with 500 or something
} finally {
// cleanup: close the opened tab, no matter how the logic resulted
await page.close()
}
})
Note that still in this scenario, the browser context would be shared across the pages, for example cookies, local storage, and so on. You have to consider this if you plan to allow concurrent requests that also can have conflicts in reusing the same shared context.
2. One browser instance per request
In this scenario you launch a new browser instance per request, you ensure each request will have a clean context and won't collide with other possible requests.
app.get("/foo", async (req, res) => {
// launch the browser instance, one per request
const browser = await puppeteer.launch({
headless: true, args: ['--no-sandbox']
});
// no need to open a new tab, reuse the first one
const [page] = await browser.pages();
try {
// ... execute some logic on the page
} catch(error) {
// whoops, logic went wrong, respond with 500 or something
} finally {
// cleanup: close the browser
// await page.close() // (not really needed if you will close the entire browser,
// and would have the same effect as browser.close()
// if you haven't opened more tabs)
await browser.close()
}
})
But consider that spining a new browser process up would also be more resource-intensive, and your request would take more time to resolve, compared to reusing an already available browser process.
EDIT: code formatting.

TestCafe: Testing a Page Opening a PDF in a New Tab

Part of the web application I am testing is to click a button, which normally opens a PDF in a new tab. This enables the user to go forward in the application. The PDF can be ignored.
Currently, my strategy is to click the button and then navigate back using ClientFunction. TestCafe successfully opens the PDF when the button is clicked, but it is opened in the same tab and the test is then stuck. Navigating back changes the URL, but the PDF is still displayed.
const goBack = ClientFunction(() => window.history.back())
await this.t.click(this.button)
await this.t.wait(10000)
await goBack()
Is TestCafe currently able to bypass this issue, as I don't need to actually do anything with the PDF?
TestCafe allows testing html pages, but not PDF files. So, you can check the generated link to the PDF file as a string without actually following this link. For example:
const overrideWindowOpen = ClientFunction(() => {
window.open = function (url) {
window.__lastWindowOpenUrl = url;
};
});
const getResultUrl = ClientFunction(() => window.__lastWindowOpenUrl);
await overrideWindowOpen();
await t
.click(this.button)
.expect(getResultUrl()).eql('http://example.com/path/to/PDF/93023813-0984-1');
See also: Multiple Browser Windows

How to close the safari pop up dialogue when running automate script with nightwatch on BrowserStack?

I use Browserstack to do the E2E testing, now I met a problem when I try to run the mobile automate script in safari on Browserstack, there will have a pop-up dialogue show when I click a button which will result in opening a new tab, the dialogue show message like this: 'this site is attempting to open a popup window', I must close it and the script can continue executing.
Now the problem is:
1. When I click the button which will trigger this pop-up dialogue, there will always show an exception in the log: 'Error while running .clickElement() protocol action: Appium error: An unknown server-side error occurred while processing the command. Original error: Did not get any response after 20s'.
2. I can use the XPath to locate the button on the pop-up dialogue and click it to close the dialogue, but it takes serval minutes, is there another way to do this operation more efficient?
const { client } = require('nightwatch-api')
const { Given, Then, When} = require('cucumber')
Given('open mobile 163 news', async function () {
await client.url('https://3g.163.com/news/article/EJN99AOF000189FH.html?clickfrom=index2018_news_newslist#offset=0')
})
When('choose share by QQ', async function () {
await client.waitForElementVisible('.sharelogo')
await client.click('.sharelogo')
})
Then('the popup should show', async function () {
await client.waitForElementVisible('.qzone')
await client.click('.qzone')
await client.setContext('NATIVE_APP')
await client.source(function(res){
console.log(res.value)
})
await client.useXpath()
await client.click('//*[#name="Allow"]')
await client.contexts(function(result) {
client.setContext(result.value[result.value.length - 1])
client.useCss()
})
})
Have you tried adding the capability 'nativeWebTap' and setting it to the value 'true' in the test scripts?

Detox: Testing a React-Native spinner with a stop button

Was wondering if anyone has had a similar issue.
In the app I'm working with, we have a spinner showing downloading content with a stop button in the middle. When the user taps the spinner/stop button, the download is meant to cancel. For reference the spinner/stop button looks like this on iOS:
I'm trying to write an e2e test for this functionality using Detox. It doesn't work using automatic synchronisation as the animation (and the download) keeps the thread running. I've tried using device.disableSynchronization() but I haven't had any success.
Here's my e2e test for reference:
it('should start and then cancel a download from the <My Learning> screen', async() => {
// setup
await device.reloadReactNative()
await expect(element(by.id('feed_screen'))).toBeVisible()
await element(by.id('LearningPlan_Tab')).tap()
await expect(element(by.id('learning-plan-screen'))).toBeVisible()
// Tap the download icon, it will turn into a spinner
await element(by.id('offline_download_c2a')).atIndex(1).tap()
// Alert box appears with Cancel/Download options
await expect(element(by.label('Download')).atIndex(1)).toBeVisible()
await element(by.label('Download')).atIndex(1).tap()
// Ideally this would work, but it doesn't (the above alert box doesn't dismiss properly)
await device.disableSynchronization()
await waitFor(element(by.id('download_spinner_c2a')).atIndex(0)).toBeVisible().withTimeout(5000)
await element(by.id('download_spinner_c2a')).atIndex(0).tap()
await device.enableSynchronization()
await element(by.label('Cancel download')).tap()
await expect(element(by.id('offline_download_c2a')).atIndex(1)).toBeVisible()
})
When this test runs the app still seems to wait for the download to finish. Does anyone know any suggestions on the best way to test this, or if it's even possible?
I've found a way to make it work, though it seems quite flaky:
it('should start and then cancel a download from the <My Learning> screen', async () => {
//...
await device.disableSynchronization()
// This timeout seems to help
await waitFor(element(by.id('download_spinner_c2a')).atIndex(0)).toBeVisible().withTimeout(10000)
await element(by.id('download_spinner_c2a')).atIndex(0).tap()
await device.enableSynchronization()
await element(by.label('Cancel download')).tap()
await expect(element(by.id('offline_download_c2a')).atIndex(1)).toBeVisible()
})

In Nightwatch, `.url()` blocks assertions for too long

It appears that using browser.url() (http://nightwatchjs.org/api/#url) to navigate causes the WebDriver client to wait until the page is fully loaded - however I have assertions I want to make before that point.
"Shows splash screen for a few seconds": function(client) {
client
.url(client.testURL)
// at this point, the splash has already come and gone, so
// this next command times out
.waitForElementVisible('#splash img', 10000)
.waitForElementNotVisible('#splash', 10000);
},
Is this possible? I think my only other option is to disable the splash in a test scenario.
I'm running these tests on Firefox v45.
You can do this by setting Firefox profile preferences as shown in below
https://github.com/nightwatchjs/nightwatch/issues/748
The preference you need to set is webdriver.load.strategy to be unstable. But this means that waiting for page to load is now all your business
var FirefoxProfile = require('firefox-profile');
function setProfile(browser, profile, callback) {
profile.encoded(function (encodedProfile) {
browser.options.desiredCapabilities['firefox_profile'] = encodedProfile;
callback();
});
}
function setFirefoxProfile(browser, done) {
var profile = new FirefoxProfile();
profile.setPreference('webdriver.load.strategy', 'unstable');
setProfile(browser, profile, done);
}
// and in my test module
before: function (browser, done) {
setFirefoxProfile(browser, done);
}