TestCafe : #shadow-root component on the webpage is not loading - testcafe

We have a web component under #shadow-root that i want to test using testcafe .
But when i try to load the page from the testcafe script, page loads but only #shadow-root component is not loading and any assertion to verify the existence of those elements fails.
Console error shows " hammerhead.js: Uncaught DOMException: Failed to execute 'setAttribute' on 'Element': 'hammerhead|element-processed' is not a valid attribute name."
Anyone knows how to enable the loading of #shadow-root components on the webpage using testcafe ?

It works fine with this example:
import { Selector } from 'testcafe';
fixture`Selector.shadowRoot`
.page`https://devexpress.github.io/testcafe/example/`;
test('Get text within shadow tree', async t => {
const shadowRoot = Selector('div').withAttribute('id', 'shadow-host').shadowRoot();
const paragraph = shadowRoot.child('p');
await t.expect(paragraph.textContent).eql('This paragraph is in the shadow tree');
await t.eval(() => {
paragraph().style.display = 'block';
}, {
dependencies: { paragraph }
})
await t.click(paragraph);
});
Refer to the following thread to see how to work with shadowRoot in TestCafe: https://testcafe.io/documentation/402829/guides/basic-guides/element-selectors?search#access-the-shadow-dom.
If this doesn't help, please share a simple sample based on the following template: https://github.com/DevExpress/testcafe/issues/new?assignees=&labels=TYPE%3A+bug&template=bug_report.yaml.

Related

Cypress e2e testing: How to access new tab by clicking on "href". I don't have target attribute to remove and test on new opened tab?

This attached image is my HTML code for href which will open a new tab
The DOM has iframe so I wrote below code accessed the href and it will open in new tab. I am unable to access the newly opened tab, though I know the method that would have target attribute so we remove that and open in same tab but here I don't have any target attributes.
Please check this and help to access my new tab.
cy.visit('https://yopmail.com/en/')
cy.get('.ycptinput').type('some_name {enter}')
cy.wait(2000)
cy.get('#ifmail').its('0.contentDocument.body').then(cy.wrap).find('a').click()
The cy.origin() command is meant to solve the "new tab" problem.
It's a bit new, so expect some teething problems. Basically, it sets up a sand-boxed domain that can use Cypress commands.
Anything from outside cy.origin() that you want to use inside (for example, the link you found) needs special handling to pass in.
It gets passed in on a special args option, and is received in the same pattern.
let link;
cy.visit('https://yopmail.com/en/')
cy.get('.ycptinput').type('some_name {enter}')
cy.wait(2000)
cy.get('#ifmail').its('0.contentDocument.body')
.then($body => {
link = $body.find('a')[0].href
})
cy.then(() => { // this just waits for above block to complete
const newOrigin = link.split('?')[0] // remove query params
.replace('http://', 'https://') // correct for secure protocol
cy.origin(newOrigin, { args: { link } }, ({ link }) => {
cy.visit(link) // same as ".find('a').click()" but works cross-domain
})
})

setValue() / addValue() type into adress bar instead of selected element

I'm using WebdriverIO + devtools:puppeteer + cucumber + Firefox Nightly.
When using setValue() / addValue(), the first letter of my input is typed into address bar, instead of selected element. The issue for same tests doesn't appear for mse or chrome browsers.
Issue:
After this, nothing happens until function timeouts
INFO devtools: COMMAND navigateTo("https://google.com/")
INFO devtools: RESULT null
INFO devtools: COMMAND findElement("css selector", "input[type=text]")
INFO devtools: RESULT { 'element-6066-11e4-a52e-4f735466cecf': 'ELEMENT-1' }
INFO devtools: COMMAND elementClear("ELEMENT-1")
INFO devtools: RESULT null
INFO devtools: COMMAND elementSendKeys("ELEMENT-1", "hello world")
Code examples:
Test:
Scenario: Try google
When I open "google.com"
Then I type "hello world" into "input[type=text]"
Steps:
When('I open {string}', async function (URL) {
await browser.url(`https://${URL}`);
});
Then('I type {string} into {string}', async function (input, selector) {
await $(selector).setValue(input);
});
Although there is a walkaround for some URLS with clicking on the element before using setValue(), this doesn't work for some cases (e.g. when redirecting from pre-login page to login page with pretyped-in login, I could not click + setValue for password field).
Hope anyone knows how this could be solved or walked around for all cases. Thanks.
[UPD]
#AnthumChris
as I'm using built-in puppeteer, page is not defined by default
Instead I tried:
const puppeteerBrowser = await browser.getPuppeteer()
const pages = await puppeteerBrowser.pages()
const page = await pages[0]
await (await page.waitForSelector('input[type=text]')).type('hello')
It worked for chrome and mse again, but failed for ffox nightly.
After opening in browser requested URL (google.com), I've received next error:
Error in "21: Then I type "hello world" into "input[type=text]""
TypeError [ERR_INVALID_URL]: Invalid URL: http://localhost:localhost:64619`
[UPD]
I've changed browserURL: 'http://localhost:${rdPort}' to browserURL: 'http://${rdPort}' in a ...\node_modules\webdriverio\build\commands\browser\getPuppeteer.js file
so I at least could connect to puppeteer.pages object, but there's still a problem on await (await page.waitForSelector('input[type=text]')).type('hello') action:
ProtocolError: Protocol error (DOM.resolveNode): Node with given id does not belong to the document resolveNode#chrome://remote/content/cdp/domains/content/DOM.jsm:245:15
execute#chrome://remote/content/cdp/domains/DomainCache.jsm:101:25
receiveMessage#chrome://remote/content/cdp/sessions/ContentProcessSession.jsm:84:45
Try awaiting the <input> and typing directly into it:
await (await page.waitForSelector('input[type=text]')).type('hello')

How to get all console messages with puppeteer? including errors, CSP violations, failed resources, etc

I am fetching a page with puppeteer that has some errors in the browser console but the puppeteer's console event is not being triggered by all of the console messages.
The puppeteer chromium browser shows multiple console messages
However, puppeteer only console logs one thing in node
Here is the script I am currently using:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
page.on('console', msg => console.log('PAGE LOG:', msg.text));
await page.goto('https://pagewithsomeconsoleerrors.com');
await browser.close();
})();
Edit: As stated in my comment below, I did try the page.waitFor(5000) command that Everettss recommended but that didn't work.
Edit2: removed spread operator from msg.text as it was there by accident.
Edit3: I opened an issue on github regarding this with similar but different example screenshots: https://github.com/GoogleChrome/puppeteer/issues/1512
The GitHub issue about capturing console erorrs includes a great comment about listening to console and network events. For example, you can register for console output and network responses and failures like this:
page
.on('console', message =>
console.log(`${message.type().substr(0, 3).toUpperCase()} ${message.text()}`))
.on('pageerror', ({ message }) => console.log(message))
.on('response', response =>
console.log(`${response.status()} ${response.url()}`))
.on('requestfailed', request =>
console.log(`${request.failure().errorText} ${request.url()}`))
And get the following output, for example:
200 'http://do.carlosesilva.com/puppeteer/'
LOG This is a standard console.log message
Error: This is an error we are forcibly throwing
at http://do.carlosesilva.com/puppeteer/:22:11
net::ERR_CONNECTION_REFUSED https://do.carlosesilva.com/http-only/sample.png
404 'http://do.carlosesilva.com/puppeteer/this-image-does-not-exist.png'
ERR Failed to load resource: the server responded with a status of 404 (Not Found)
See also types of console messages received with the console event and response, request and failure objects received with other events.
If you want to pimp your output with some colours, you can add chalk, kleur, colorette or others:
const { blue, cyan, green, magenta, red, yellow } = require('colorette')
page
.on('console', message => {
const type = message.type().substr(0, 3).toUpperCase()
const colors = {
LOG: text => text,
ERR: red,
WAR: yellow,
INF: cyan
}
const color = colors[type] || blue
console.log(color(`${type} ${message.text()}`))
})
.on('pageerror', ({ message }) => console.log(red(message)))
.on('response', response =>
console.log(green(`${response.status()} ${response.url()}`)))
.on('requestfailed', request =>
console.log(magenta(`${request.failure().errorText} ${request.url()}`)))
The examples above use Puppeteer API v2.0.0.
Easiest way to capture all console messages is passing the dumpio argument to puppeteer.launch().
From Puppeteer API docs:
dumpio: <boolean> Whether to pipe the browser process stdout and stderr
into process.stdout and process.stderr. Defaults to false.
Example code:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
dumpio: true
});
...
})();
You need to set multiple listeners if you want to catch everything. The console event is emitted when javascript within the page calls a console API message (like console.log).
For a full list of the console API take a look at the docs for console on MDN:
https://developer.mozilla.org/en-US/docs/Web/API/Console
The reason you need multiple listeners is because some of what is being logged in the image you posted is not happening within the page.
So for example, to catch the first error in the image, net:: ERR_CONNECTION_REFUSED you would set the listener like so:
page.on('requestfailed', err => console.log(err));
Puppeteer's documentation contains a full list of events. You should take a look at the documentation for the version you are using and look at the different events the Page class will emit as well as what those events will return. The example I've written above will return an instance of Puppeteer's request class.
https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-page
I extended Ferdinand's great comment code with descriptive errors text from JSHandle consoleMessage object in next comment. So you can catch all the errors from the browser and read all the description like in browser console.
Check it out there https://stackoverflow.com/a/66801550/9026103

Puppeteer Element Handle loses context when navigating

What I'm trying to do:
I'm trying to get a screenshot of every element example in my storybooks project. The way I'm trying to do this is by clicking on the element and then taking the screenshot, clicking on the next one, screenshot etc.
Here is the attached code:
test('no visual regression for button', async () => {
const selector = 'a[href*="?selectedKind=Buttons&selectedStory="]';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('http://localhost:8080');
let examples = await page.$$(selector);
await examples.map( async(example) => {
await example.click();
const screen = await page.screenshot();
expect(screen).toMatchImageSnapshot();
});
await browser.close();
});
But when I run this code I get the following error:
Protocol error (Runtime.callFunctionOn): Target closed.
at Session._onClosed (../../node_modules/puppeteer/lib/Connection.js:209:23)
at Connection._onClose (../../node_modules/puppeteer/lib/Connection.js:116:15)
at Connection.dispose (../../node_modules/puppeteer/lib/Connection.js:121:10)
at Browser.close (../../node_modules/puppeteer/lib/Browser.js:60:22)
at Object.<anonymous>.test (__tests__/visual.spec.js:21:17)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:169:7)
I believe it is because the element loses its context or something similar and I don't know what methods to use to get around this. Could you provide a deeper explanation or a possible solution? I don't find the API docs helpful at all.
ElementHandle.dispose() is called once page navigation occurs as garbage collection as stated here in the docs. So when you call element.click() it navigates and the rest of the elements no longer point to anything.

Facing Issue on Getting Current Tabs and Selected Tab URL on Chrome API

I am trying to get the URL of all opened Tabs and Selected Tab using thiese two snippet
// For Getting URL of All Opened Tabs
chrome.tabs.getCurrent(function(tabs) {
for (var i = 0; i < tabs.length; i++) {
console.log(tab.url);
}
});
// For Getting URL of Selected Tab
chrome.tabs.getSelected(function(tab) {
console.log(tab.url);
});
but neither of them working. For getting All Tabs I am getting this error:
Error in response to tabs.getCurrent: TypeError: Cannot read property 'length' of undefined
and for getting the selected Tab
undefined
can you please let me know why this is happening and how can I fix it?
chrome.tabs.getSelected has been deprecated. so we should use tabs.query({active: true}... instead.
chrome.tabs.getCurrent passes a single tab to the callback function. It doesn't "Getting URL of All Opened Tabs", it:
Gets the tab that this script call is being made from. May be undefined if called from a non-tab context (for example: a background page or popup view).
So:
// Write the URL of the current tab to the console
chrome.tabs.getCurrent(tab => console.log(tab.url));
This requires the "activeTab" or "tabs" permission in the manifest. If there is an error it won't throw an exception, instead it will populate chrome.runtime.lastError.
I find it easier to deal with all the callbacks using an asynchronous or promise wrapper library like chrome-extension-async. This let's us use async/await syntax and a regular try-catch:
try {
const currentTab = await chrome.tabs.getCurrent();
console.log(currentTab.url);
}
catch(err) {
// Handle errors
}
In your popup.html you can't access chrome.tabs.getCurrent - you have to use chrome.tabs.query instead:
async function writeAllTabUrlToConsole() {
try {
// Get all the tabs
const tabs = await chrome.tabs.query({});
// Write all their URLs to the popup's console
for(let t of tabs)
console.log(t.url, t.active);
}
catch(err) {
// Handle errors
}
}