How to clear browser cache within a Cypress test - javascript

Background:
Hey all, I have a Cypress test I'm trying to write that should check whether an image URL has been called, after the image itself has been clicked on.
Currently I run the test and it finds the image URL on the first run but when it is re-run, the test fails to find it because the image has already been saved to the browser cache.
Is there a way to clear the browser cache before each Cypress test so that the image URL will always get called/found when I run the test?
Current solution:
it('should request webp image format for zoomed pdp image', () => {
cy.intercept(
'**/cdn/cs/set/assets/blt2bd0405f3cb027cb/5005769.jpg?fit=bounds&format=webply&quality=80&width=1600&height=1600&dpr=1.5'
).as('zoomedImageRequest');
cy.gotoPage('product/transparent-red-sorting-case-to-go-5005769');
cy.get(pdp.pdpImage).click();
cy.wait('#zoomedImageRequest').then((xhr) => {
const zoomedImageUrl = xhr.response.url;
expect(zoomedImageUrl).to.contain('format=webply');
});
});

Try clearing the network cache at the top of the test,
cy.wrap(Cypress.automation('remote:debugger:protocol', {
command: 'Network.clearBrowserCache',
}))
cy.intercept(...).as('zoomedImageRequest');
cy.gotoPage('product/transparent-red-sorting-case-to-go-5005769');
cy.get(pdp.pdpImage).click();
cy.wait('#zoomedImageRequest').then((xhr) => {
const zoomedImageUrl = xhr.response.url;
expect(zoomedImageUrl).to.contain('format=webply');
});
There is also a header that can be removed, but I found it not to be consistent
cy.intercept('...', req => {
delete req.headers['if-none-match'];
})

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 do I bypass plaid iframe with Cypress.io?

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();
});

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.

Checking if File Was Downloaded on Windows With Protractor

I am trying to run a test where a file gets downloaded from our web application. The problem that I am running into however is that the file already exists and I have an if statement to handle it and delete it prior to downloading another one. The problem is though that it never goes into the if statement.
I know that my file path is correct. Is there something else I am doing wrong here?
const fs = require('fs')
fit('Download Visible Records', () => {
const filename = `C:\\Users\\me\\Downloads\\DataExport_2017-04-18-10-04am.csv`
if (fs.existsSync(filename)) {
console.log('Are we getting here??') // NEVER ACTUALLY GETS HERE EVEN THOUGH THE FILE DOES EXIST
// Make sure the browser doesn't have to rename the download
fs.unlink(filename)
console.log('how about here?')
}
claims.downloadVizRecords()
browser.driver.wait(() => {
expect(fs.existsSync(filename)).toBe(true)
}, 10000)
})
My code works fine:
if (fs.existsSync('D:/CI/script/result/Report.html')) {
fs.unlinkSync('D:/CI/script/result/Report.html');
}

Re-using same instance again webdriverJS

I am really new to Selenium. I managed to open a website using the below nodejs code
var webdriver = require('selenium-webdriver');
var driver = new webdriver.Builder()
.forBrowser('chrome')
.build();
console.log(driver);
driver.get('https://web.whatsapp.com');
//perform all other operations here.
https://web.whatsapp.com is opened and I manually scan a QR code and log in. Now I have different javascript files to perform actions like delete, clear chat inside web.whatsapp.com etc...
Now If I get some error, I debug and when I run the script again using node test.js, it takes another 2 minutes to load page and do the steps I needed. I just wanted to reopen the already opened tab and continue my script instead new window opens.
Edit day 2 : Still searching for solution. I tried below code to save object and reuse it.. Is this the correct approach ? I get a JSON parse error though.
var o = new chrome.Options();
o.addArguments("user-data-dir=/Users/vishnu/Library/Application Support/Google/Chrome/Profile 2");
o.addArguments("disable-infobars");
o.addArguments("--no-first-run");
var driver = new webdriver.Builder().withCapabilities(webdriver.Capabilities.chrome()).setChromeOptions(o).build();
var savefile = fs.writeFile('data.json', JSON.stringify(util.inspect(driver)) , 'utf-8');
var parsedJSON = require('./data.json');
console.log(parsedJSON);
It took me some time and a couple of different approaches, but I managed to work up something I think solves your problem and allows to develop tests in a rather nice way.
Because it does not directly answer the question of how to re-use a browser session in Selenium (using their JavaScript API), I will first present my proposed solution and then briefly discuss the other approaches I tried. It may give someone else an idea and help them to solve this problem in a nicer/better way. Who knows. At least my attempts will be documented.
Proposed solution (tested and works)
Because I did not manage to actually reuse a browser session (see below), I figured I could try something else. The approach will be the following.
Idea
Have a main loop in one file (say init.js) and tests in a separate file (test.js).
The main loop opens a browser instance and keeps it open. It also exposes some sort of CLI that allows one to run tests (from test.js), inspect errors as they occur and to close the browser instance and stop the main loop.
The test in test.js exports a test function that is being executed by the main loop. It is passed a driver instance to work with. Any errors that occur here are being caught by the main loop.
Because the browser instance is opened only once, we have to do the manual process of authenticating with WhatsApp (scanning a QR code) only once. After that, running a test will reload web.whatsapp.com, but it will have remembered that we authenticated and thus immediately be able to run whatever tests we define in test.js.
In order to keep the main loop alive, it is vital that we catch each and every error that might occur in our tests. I unfortunately had to resort to uncaughtException for that.
Implementation
This is the implementation of the above idea I came up with. It is possible to make this much fancier if you would want to do so. I went for simplicity here (hope I managed).
init.js
This is the main loop from the above idea.
var webdriver = require('selenium-webdriver'),
by = webdriver.By,
until = webdriver.until,
driver = null,
prompt = '> ',
testPath = 'test.js',
lastError = null;
function initDriver() {
return new Promise((resolve, reject) => {
// already opened a browser? done
if (driver !== null) {
resolve();
return;
}
// open a new browser, let user scan QR code
driver = new webdriver.Builder().forBrowser('chrome').build();
driver.get('https://web.whatsapp.com');
process.stdout.write("Please scan the QR code within 30 seconds...\n");
driver.wait(until.elementLocated(by.className('chat')), 30000)
.then(() => resolve())
.catch((timeout) => {
process.stdout.write("\b\bTimed out waiting for code to" +
" be scanned.\n");
driver.quit();
reject();
});
});
}
function recordError(err) {
process.stderr.write(err.name + ': ' + err.message + "\n");
lastError = err;
// let user know that test failed
process.stdout.write("Test failed!\n");
// indicate we are ready to read the next command
process.stdout.write(prompt);
}
process.stdout.write(prompt);
process.stdin.setEncoding('utf8');
process.stdin.on('readable', () => {
var chunk = process.stdin.read();
if (chunk === null) {
// happens on initialization, ignore
return;
}
// do various different things for different commands
var line = chunk.trim(),
cmds = line.split(/\s+/);
switch (cmds[0]) {
case 'error':
// print last error, when applicable
if (lastError !== null) {
console.log(lastError);
}
// indicate we are ready to read the next command
process.stdout.write(prompt);
break;
case 'run':
// open a browser if we didn't yet, execute tests
initDriver().then(() => {
// carefully load test code, report SyntaxError when applicable
var file = (cmds.length === 1 ? testPath : cmds[1] + '.js');
try {
var test = require('./' + file);
} catch (err) {
recordError(err);
return;
} finally {
// force node to read the test code again when we
// require it in the future
delete require.cache[__dirname + '/' + file];
}
// carefully execute tests, report errors when applicable
test.execute(driver, by, until)
.then(() => {
// indicate we are ready to read the next command
process.stdout.write(prompt);
})
.catch(recordError);
}).catch(() => process.stdin.destroy());
break;
case 'quit':
// close browser if it was opened and stop this process
if (driver !== null) {
driver.quit();
}
process.stdin.destroy();
return;
}
});
// some errors somehow still escape all catches we have...
process.on('uncaughtException', recordError);
test.js
This is the test from the above idea. I wrote some things just to test the main loop and some WebDriver functionality. Pretty much anything is possible here. I have used promises to make test execution work nicely with the main loop.
var driver, by, until,
timeout = 5000;
function waitAndClickElement(selector, index = 0) {
driver.wait(until.elementLocated(by.css(selector)), timeout)
.then(() => {
driver.findElements(by.css(selector)).then((els) => {
var element = els[index];
driver.wait(until.elementIsVisible(element), timeout);
element.click();
});
});
}
exports.execute = function(d, b, u) {
// make globally accessible for ease of use
driver = d;
by = b;
until = u;
// actual test as a promise
return new Promise((resolve, reject) => {
// open site
driver.get('https://web.whatsapp.com');
// make sure it loads fine
driver.wait(until.elementLocated(by.className('chat')), timeout);
driver.wait(until.elementIsVisible(
driver.findElement(by.className('chat'))), timeout);
// open menu
waitAndClickElement('.icon.icon-menu');
// click profile link
waitAndClickElement('.menu-shortcut', 1);
// give profile time to animate
// this prevents an error from occurring when we try to click the close
// button while it is still being animated (workaround/hack!)
driver.sleep(500);
// close profile
waitAndClickElement('.btn-close-drawer');
driver.sleep(500); // same for hiding profile
// click some chat
waitAndClickElement('.chat', 3);
// let main script know we are done successfully
// we do so after all other webdriver promise have resolved by creating
// another webdriver promise and hooking into its resolve
driver.wait(until.elementLocated(by.className('chat')), timeout)
.then(() => resolve());
});
};
Example output
Here is some example output. The first invocation of run test will open up an instance of Chrome. Other invocations will use that same instance. When an error occurs, it can be inspected as shown. Executing quit will close the browser instance and quit the main loop.
$ node init.js
> run test
> run test
WebDriverError: unknown error: Element <div class="chat">...</div> is not clickable at point (163, 432). Other element would receive the click: <div dir="auto" contenteditable="false" class="input input-text">...</div>
(Session info: chrome=57.0.2987.133)
(Driver info: chromedriver=2.29.461571 (8a88bbe0775e2a23afda0ceaf2ef7ee74e822cc5),platform=Linux 4.9.0-2-amd64 x86_64)
Test failed!
> error
<prints complete stacktrace>
> run test
> quit
You can run tests in other files by simply calling them. Say you have a file test-foo.js, then execute run test-foo in the above prompt to run it. All tests will share the same Chrome instance.
Failed attempt #1: saving and restoring storage
When inspecting the page using my development tools, I noticed that it appears to use the localStorage. It is possible to export this as JSON and write it to a file. On a next invocation, this file can be read, parsed and written to the new browser instance storage before reloading the page.
Unfortunately, WhatsApp still required me to scan the QR code. I have tried to figure out what I missed (cookies, sessionStorage, ...), but did not manage. It is possible that WhatsApp registers the browser as being disconnected after some time has passed. Or that it uses other browser properties (session ID?) to recognize the browser. This is pure speculating from my side though.
Failed attempt #2: switching session/window
Every browser instance started via WebDriver has a session ID. This ID can be retrieved, so I figured it may be possible to start a session and then connect to it from the test cases, which would then be run from a separate file (you can see this is the predecessor of the final solution). Unfortunately, I have not been able to figure out a way to set the session ID. This may actually be a security concern, I am not sure. People more expert in the usage of WebDriver might be able to clarify here.
I did find out that it is possible to retrieve a list of window handles and switch between them. Unfortunately, windows are only shared within a single session and not across sessions.

Categories

Resources