How do I bypass plaid iframe with Cypress.io? - javascript

I'm currently doing some automation, and I'm having trouble getting over the Plaid iframe. This how it looks inside of my app:
This how is setup inside of my app:
<div class="PaneActions PaneActions--no-padding-top"><button
class="TextButton TextButton--is-action TextButton--is-threads-treatment TextButton--no-margin">
<p class="FinePrint FinePrint--is-threads-treatment">By selecting “Continue” you agree to the <u>Plaid End User
Privacy Policy</u></p>
</button><button
class="Touchable-module_resetButtonOrLink__hwe7O Touchable-module_block__WBbZm Touchable-module_wide__EYer3 Button-module_button__1yqRw Button-module_large__1nbMn Button-module_centered__3BGqS"
id="aut-continue-button" type="button" role="button"><span class="Button-module_flex__2To5J"><span
class="Button-module_text__38wV0">Continue</span></span></button></div>
I'm getting the parent and the child elements, I'm looking by the text, and many other options and I'm unable to test this product. Does anyone has been working with plaid before?

Using the Plaid demo page as a test app and following the steps in Working with iframes in Cypress, I managed to get a consistently working test.
From the blog, I used this sequence to ensure the iframe body has fully loaded
iframe -> body -> should.not.be.empty
The page loads a placeholder first while is waits for a GET request to complete, so just getting a loaded iframe body is not sufficient.
placeholder
<p>iframe placeholder for https://cdn.plaid.com/link/v2/stable/link.html?isLinkInitialize=true&origin=https%3A%2F%2Fplaid.com&token=link-sandbox-170bce6a-fe90-44a4-8b8a-54064fbc8032&uniqueId=1&version=2.0.917</p>
We need to wait for the "Continue" button, which takes a bit of time to show so give it a long timeout.
Using .contains('button', 'Continue', { timeout: 10000 }) actually returned two button, although there is only one visible.
I changed the button selector to use an id and all works consistently.
The test
cy.visit('https://plaid.com/demo/?countryCode=US&language=en&product=transactions');
cy.contains('button', 'Launch Demo').click()
cy.get('iframe#plaid-link-iframe-1', { timeout: 30000 }).then(iframe => {
cy.wrap(iframe) // from Cypress blog ref above
.its('0.contentDocument.body')
.should('not.be.empty') // ensure the iframe body is loaded
.as('iframeBody') // save for further commands within iframe.body
//.contains('button', 'Continue', { timeout: 10000 }) // returns 2 buttons!
.find('button#aut-continue-button', { timeout: 10000 }) // this is the best selector
.click()
cy.get('#iframeBody')
.contains('h1', 'Select your bank') // confirm the result of the click()
})

You will have to do the call for your app link
You will have to add the following code:
describe('Plad Testing', () => {
it('Request Loan', () => {
//cy.find('Button-module_large__1nbMn', [0], { timeout: 10000 }).click()
cy.visit('https://plaid.com/demo/?countryCode=US&language=en&product=transactions');
cy.contains('button', 'Launch Demo').click()
cy.get('iframe#plaid-link-iframe-1', { timeout: 30000 }).then(iframe => {
let plaid = cy.wrap(iframe)
plaid // from Cypress blog ref above
.its('0.contentDocument.body')
.should('not.be.empty') // ensure the iframe body is loaded
.as('iframeBody') // save for further commands within iframe.body
//.contains('button', 'Continue', { timeout: 10000 }) // returns 2 buttons!
.find('button#aut-continue-button', { timeout: 10000 }) // this is the best selector
.click()
let plaid_choose_bank = cy.get('#iframeBody')
plaid_choose_bank
.contains('h1', 'Select your bank') // confirm the result of the click()
.xpath('/html/body/reach-portal/div[3]/div/div/div/div/div/div/div[2]/div[2]/div[2]/div/ul/li[1]/button/div/div[2]/p[1]').click()
let plaid_bank_username = cy.get('#iframeBody')
plaid_bank_username
.find('input[name="username"]').type('user_good', { delay: 100 })
let plaid_bank_password = cy.get('#iframeBody')
plaid_bank_password
.find('input[name="password"]').type('pass_good', { delay: 100 })
let plaid_bank_button = cy.get('#iframeBody')
plaid_bank_button
.find('button#aut-submit-button').click()
})
})
})
It could be possible that you get that you are not able to find the body of your iFrame. To solve this issue, we will need to add some configuration to the Cypress.json file:
{
"chromeWebSecurity": false,
"pageLoadTimeout": 300000,
"viewportWidth": 1536,
"viewportHeight": 960,
"includeShadowDom": true,
}
Chrome web security will prevent any CORS security from fire-up inside of the current test scenario that we have since you will have that 0.contentDocument.body will return null if the parent origin is different from the iframe origin. This will cause the CORS security issue!
Page load time will help to slow loading the pages and have more time to process things
Viewport will help to make the browser window render like a laptop screen
Include shadow dom will make it easier to look for this type of element without including the "includeShadowDom" inside of your find() elements.

All of the other answers here use *.plaid.com as the origin, which is why contentDocument is not null.
If you are testing this in the real world, you will be running a cross-origin iframe which causes contentDocument to be null a per the MDN page
Cypress is in the process of adding official iframe support, but until then you can use cypress-iframe which just worked for me out of the box.
My test
it.only('users should be able to import holdings from their broker', () => {
cy.visit('/portfolios/created');
cy.findByText('Import from broker').click();
cy.frameLoaded();
cy.iframe().findByText('Continue').click();
});

Related

App in cypress redirects, outside does not

I am trying to write end to end tests for this application with Cypress: https://app.gotphoto.com/admin/auth/login
When I visit the above url from my browswer, a login form is showing, as expected.
When I visit the above url through Cypress:
cypress first navigates to https://app.gotphoto.com/admin/auth/login
immediately afterwards I am redirected to https://app.gotphoto.com/__/ and the login form is not showing
These are two screenshots from inside Cypress:
My question is: why is there a difference between how it runs in my browser and how it runs in Cypress / Cypress's browswer?
The browswer I am using is Chrome 89, both when running with and without Cypress.
The entirety of the test I am running is this:
describe('login screen', () => {
it('logs in', () => {
cy.visit('/admin/auth/login');
});
});
with a cypress.json:
{
"baseUrl": "https://app.gotphoto.com"
}
I created a repo with the above configuration so it's simple to reproduce.
The /__/ portion of https://app.gotphoto.com/__/ is called the clientRoute and is an internal configuration item in Cypress.
You can turn it off in your cypress.json configuration file
{
...
"clientRoute": "/"
}
This effectively keeps your original url and allows the page to load properly.
cy.visit('https://app.gotphoto.com/admin/auth/login')
cy.get('input#username', { timeout: 10000 }).type('admin') // long timeout
// wait for page to load
cy.get('input#password').type('password')
cy.intercept('POST', 'api.getphoto.io/v4/auth/login/user').as('user')
cy.contains('button', 'Submit').click()
cy.wait('#user').then(interception => {
// incorrect credentials
expect(interception.response.body.detail).to.eq('Login failed!')
})
I'm not sure of any bad side effects of changing clientRoute, will post more information if I find it.
That redirect to __/ sounds familiar to an issue I stumbled upon some time ago. I found this comment in one of Cypress' issues quite helpful.
So did you already try to use the configuration option experimentalSourceRewriting? In your cypress.json, it may look like this:
{
"baseUrl": "https://app.gotphoto.com"
"experimentalSourceRewriting": true
}
As it's labelled experimental, I'd recommend testing it carefully but maybe it helps a bit. I hope for the best! 🙏
why is there a difference between how it runs in my browser and how it runs in Cypress / Cypress's browser?
Your normal browser waits for the XHR requests to be completed and renders the final output created by whatever js magic you have written in there but cy.visit is not supposed to wait for those XHR / AJAX requests inside. It gets 200 in response and moves ahead. If you add a cypress command next to cy.visit, something like cy.get('h1'), you will notice that this command runs instantly after cy.visit, and after that, your XHR requests are resolved.
One work around here can be to use cy.intercept, for example (Cypress 6.8.0, Chrome 89):
describe("login screen", () => {
it("logs in", () => {
cy.intercept({
method: "GET",
url: "admin/version/master/index.html"
}).as("indexHTML"); // Similarly add other internal xhr requests
cy.visit("/admin/auth/login");
cy.wait("#indexHTML").then(interception => {
expect(interception.response.statusCode).to.be.eq(200);
});
});
});
Output:
It basically waits for your internal XHR requests to finish and allows you to play with the request and responses once they are resolved.
This issue will help you debug further: https://github.com/cypress-io/cypress/issues/4383
Also, this /__/ has no hand in rendering the blank page IMO.
An example of logging in. Ultimately this is a bit of a hacky solution as it fails on the very first try; however, it works on any subsequent attempt.
Add the following to your command.js
// -- Visit multiple domains in one test
Cypress.Commands.add('forceVisit', url => {
cy.window().then(win => {
return win.open(url, '_self');
});
});
login.spec.js
describe('login screen', () => {
it('logs in', {
retries: {
runMode: 1,
openMode: 1
}
}, () => {
cy.forceVisit('https://app.gotphoto.com/admin/auth/login');
cy.get('#username').should('exist');
});
});
Screenshot:

How to test programatically chosen external URL with Cypress for Bootstrap4 page

I am getting to grips with Cypress. Loving it so far, however, I have got stuck with the following.
I want to test that when a button is clicked, the user is directed to the correct external site. This works fine (at the moment) using the following code:
$("#my-button").click(function() {
var external_url = 'https://www.somesite.com/';
if($("#my-checkbox").prop("checked")) {
external_url += 'foo';
} else {
external_url += 'bar';
}
window.location.href = external_url;
return false;
});
Starting small within a Cypress spec:
it('Should direct to external site depending on checkbox state', () => {
cy.get('#my-button').click()
})
Receives Cypress Error:
Cypress detected a cross origin error happened on page load:
Blocked a frame with origin "http://localhost:8888" from accessing a cross-origin frame.
This is fair enough, and I understand why I get the error. I don't want to disable Chrome Web Security to get around it.
Can someone show me how I should test this use case?
I think I should be trapping the location change with cy.stub() and/or cy.on() but so far I have not had success:
it('Should direct to external site depending on checkbox state', () => {
cy.stub(Cypress.$("#my-button"), "click", () => {
console.log('stub')
})
cy.on("window.location.href", win => {
console.log('on')
})
cy.get('#my-button').click()
})
The button click still results in the error being thrown as the script still attempts to set the location to an external site.

Selenium element is not clickable at point (chrome) in javascript

I'm new to Selenium and I'm running my selenium script on Browserstack.
Everything works fine, until i reach the bottom 10% of my page.
I get the following error:
Uncaught WebDriverError: Appium error: unknown error: Element is not clickable at point (20, 324). Other
element would receive the click: ...
(Session info: chrome=58.0.3029.83)
(Driver info: chromedriver=2.29.461571 (8a88bbe0775e2a23afda0ceaf2ef7ee74e822cc5),platform=Linux
3.19.8-100.fc20.x86_64 x86_64)
This is my code:
describe.only(testTitle, function () {
before(function () {
driver = driverConfiguration.getDriverConfiguration(testTitle, 'chrome')
})
after(function (done) {
driver.quit().then(done)
})
it('Sample tests', function (done) {
driver.get('https://www.test.com').then(function(){
driver.findElement(webdriver.By.name('cardNumber')).sendKeys('0000000000').then(function(){
driver.findElement(webdriver.By.id('billingLine1')).sendKeys('test');
driver.findElement(webdriver.By.id('billingLine2')).sendKeys('test');
driver.findElement(webdriver.By.id('billingCity')).sendKeys('San Jose');
driver.findElement(webdriver.By.id('agree')).click(); // ERROR!!!!!
}).then(function() {
driver.quit().then(done);
})
});
})
})
When I do the following:
// return driver.wait(function() {
// return driver.findElement(webdriver.By.id('agree')).isDisplayed();
// }, 1000);
It says True. The element is visible.
Using Chrome on Samsung Galaxy S8
I'm not sure how to solve this problem.
You've omitted the most important part of the error message in your question
Other element would receive the click: ...
The element in the ... section was the element that was blocking the click. As you discovered, Selenium was reporting that the element was displayed/visible. This error message is just stating that when Selenium attempted to click on the element, another element was blocking the click. If you take a look at the HTML of the blocking element and search that in the HTML of the page, you should be able to identify the element. In my experience, it's a dialog or maybe a banner at the bottom of the page, etc. Sometimes you will need to close it, other times you will need to scroll down/up a little to get the desired element from behind the blocking UI.
Continued from comments above ...
I encountered this problem as well, when I needed to click a button but it was not visible on the screen (however, it was detected by the code).
To resolve this, I used the WebDriver's executeScript() method to run some JavaScript on the page to scroll until my button was in view.
driver.executeScript(`
var target = document.getElementById('agree');
var tarTop = target.getBoundingClientRect().top;
document.body.scrollTop = tarTop;
`);
You can try driver.executeAsyncScript() if you want want to add a timeout to the scroll, to make sure the page has reached its destination first. At that point you'll be using async/await...
await driver.executeAsyncScript(`
var callback = arguments[arguments.length - 1];
var target = document.getElementById('agree');
var tarTop = target.getBoundingClientRect().top;
document.body.scrollTop = tarTop;
setTimeout(()=>{ callback( true ); }, 1500);
`).then((data)=>{
return data;
});

Repeatedly clicking an element on a page using Electron (NightareJS)

I'm writing a page scraper for a dynamic web page. The page has an initial load and then loads the remainder of the content after a short load time.
I've accounted for the load and have successfully scraped the HTML from the page, but the page doesn't load ALL the content at once. Instead it loads a specified amount of content via GET request URL and then has a "Get more" button on the page. My objective is to click this "Get More" button until all the content is loaded on the page. For those wondering, I don't wish to load all the content at once via GET URL because of impact to their server.
I'm stuck forming the loop or iteration that would allow me to repeatedly click on the page.
const NIGHTMARE = require("nightmare");
const BETHESDA = NIGHTMARE({ show: true });
BETHESDA
// Open the bethesda web page. Web page will contain 20 mods to start.
.goto("https://bethesda.net/en/mods/skyrim?number_results=40&order=desc&page=1&platform=XB1&product=skyrim&sort=published&text=")
// Bethesda website serves all requested mods at once. Each mod has the class "tile". Wait for any tile class to appear, then proceed.
.wait(".tile");
let additionalModsPresent = true;
while(additionalModsPresent) {
setTimeout(function() {
BETHESDA
.wait('div[data-is="main-mods-pager"] > button')
.click('div[data-is="main-mods-pager"] > button')
}, 10000)
additionalModsPresent = false;
}
// let moreModsBtn = document.querySelector('div[data-is="main-mods-pager"] > button');
// .end()
BETHESDA.catch(function (error) {
console.error('Search failed:', error);
});
My thinking thus far has been to use a while loop that attempts to click the button after some interval of time. If an error occurs, it's likely because the button doesn't exist. The issue I'm having is that I can't seem to get the click to work inside of a setTimeout or setInterval. I believe there is some sort of scoping issue but I don't know what exactly is going on.
If I can get the click method to work in setInterval or something similar, the issue would be solved.
Thoughts?
You can refer to the issue (Problem running nightmare in loops)[https://github.com/segmentio/nightmare/issues/522]
I modified your code with given guidelines. It seem to work fine
const NIGHTMARE = require("nightmare");
const BETHESDA = NIGHTMARE({
show: true
});
BETHESDA
// Open the bethesda web page. Web page will contain 20 mods to start.
.goto("https://bethesda.net/en/mods/skyrim?number_results=40&order=desc&page=1&platform=XB1&product=skyrim&sort=published&text=")
// Bethesda website serves all requested mods at once. Each mod has the class "tile". Wait for any tile class to appear, then proceed.
.wait(".tile");
next();
function next() {
BETHESDA.wait('div[data-is="main-mods-pager"] > button')
.click('div[data-is="main-mods-pager"] > button')
.then(function() {
console.log("click done");
next();
})
.catch(function(err) {
console.log(err);
console.log("All done.");
});
}
Ultimately, it should timeout on wait() for button and then you can handle the error in catch() block. Beware it goes on and on :) I did not wait till the end (you might run out of memory).

Unable to find element after login action

I am writing some automation using NightWatchJS. I have a simple test scenario where i do following stuff
Open Url
Login
Verify elements
Verify Iframe Elements
I have simple login page with submit button. The inner page, after login, has iframe as well. As soon as the login action success, the textfield in the iframe is focused with javascript.
My NightWatch test code
module.exports = {
before: function(browser) {
console.log("Setting up...");
browser
.maximizeWindow()
},
after : function(browser) {
browser
.end()
console.log("Closing down...");
},
'Check Form Elements' : function (browser) {
browser
.url("siteurl")
.waitForElementVisible("body", 1000)
.waitForElementPresent('#email', 10000)
.setValue('#email','me#a5his.com')
.waitForElementPresent('#password', 10000)
.setValue('#password', 'encrypted')
.submitForm('#login_form', function(result){
console.log(result)
})
.pause(5000)
.waitForElementVisible('body', 10000)
.waitForElementVisible('li.active', 5000)
.assert.containsText('li.active', "Home")
.assert.elementPresent('#iframeone')
.frame('iframeone')
.waitForElementVisible('.wi-wrap', 5000)
. assert.containsText('header h1', 'Messages')
}
};
This test passes till form submission after that it fails saying
Timed out while waiting for element <body> to be present for 10000 milliseconds. - expected "visible" but got: not found
I am using Chrome 50, ChromeDriver 2.21.371459, OS X - El Capitan.
I have attached my selenium details below.
Thanks in advance.
Is your page redirect after successfully login ?
Can you try this :
browser
.url("siteurl")
.waitForElementVisible('#email', 10000,false)
.setValue('#email','me#a5his.com')
.setValue('#password', 'encrypted')
.click('#login_form')
.waitForElementVisible('li.active', 5000,false)
.verify.containsText('li.active', "Home")
.verify.elementPresent('#iframeone')
.frame('#iframeone')
.waitForElementVisible('.wi-wrap', 5000,false)
.verify.containsText('header h1', 'Messages')
Rememeber if the frame id is missing or null, the server should switch to the page's default content. So better the selector should be like below to make sure it will be there and unique .
'input[name=username]',
'button[name=login]',
'iframe[class=iframeone]'
Edit 1: For the element 'body' ,you should check 'present or not' instead of 'visible or not'
try waitForElementPresent('body',5000,false)
instead of
waitForElementVisible('body',5000,false)

Categories

Resources