Using something other than cy.request to seed data in Cypress - cypress

I'm using Cypress for end-to-end testing. In my beforeEach, I'm using an SDK I've been provided to seed data on a server (the SDK sends API calls to the server but does not use cy.request inside it). The method on the SDK returns a promise, therefore I figured I could return the promise like so:
beforeEach(() => {
return sdk.createProperty(...);
});
My test then does something like this:
it('displays a property', () => {
cy.visit(`/companies/${appTestData.companyId}/properties`);
...the rest is commented out currently...
}
This actually works as intended, that is, it waits until the server response is returned before running the tests, but I see the following warning in the console when the test actually runs:
Cypress Warning: Cypress detected that you returned a promise in a test, but also invoked one or more cy commands inside of that promise...
I noticed if I change my beforeEach to use cy.then, the warning goes away:
beforeEach(() => {
cy.then(() => sdk.createProperty(...));
});
It seems a bit unnecessary and was kind of a stab in the dark, so I'd like to know if there's a prescribed way of doing what I need to do. I can't change the SDK I'm using to use cy.request, which I assume would also prevent the warning. Thanks.

Probably not what you want to hear, but can I confirm you that using cy.then(...) is the most standard way of waiting for a Promise in Cypress I know of.
After reading your question, I have tried to use Cypress Network Requests features to wait for a fetch('my/url') in a before(), but it doesn't seem to be detecting the request at all.

Related

Cypress tests failing in firefox in pages with polling requests

I have a following test in Cypress:
visit first page with the header A
click on the Go to B Page button
assert that the header of the page is now B
It works fine in Chrome, but failing in Firefox, as on the page B I have some background polling requests, and when cypress switches to another test and those requests get "canceled" away, I get either TypeError: NetworkError when attempting to fetch resource or AbortError: The operation was aborted
All the requests are using fetch api, by the way.
The possibility to mute those errors through the uncaught:exception event seems a bad idea, and so does the idea to do something on the page to cancel the polling, as it is not the thing under testing.
Maybe someone has encoutnered this problem too and got some non-hacky solution?
I had a similar issue with Cypress tests in Firefox and resorted to the slightly hacky solution of using an uncaught:exception handler as you mention. It is possible to filter error messages somewhat at least:
function handleUncaughtException(err){
if (err.message.includes('Request aborted') ) {
console.log("Request aborted. Test will continue. Error:",err);
return false; // return false to make test continue
}
throw err;
}
cy.on('uncaught:exception',handleUncaughtException);
In principle you can cancel this handler when it's no longer needed. In my case though, this stopped the test working, presumably because the request started previous to or after the calls.
cy.removeListener("uncaught:exception", handleUncaughtException)
The Cypress docs have some advice on defining these: see at https://docs.cypress.io/api/events/catalog-of-events#Examples. It may be useful to put the handler in a support file, so that it is applied to all tests.
(See also https://docs.cypress.io/api/events/catalog-of-events#Event-Types and https://nodejs.org/api/events.html#events_emitter_removelistener_eventname_listener).

I'd like to use Async/await and onPrepare to make sure my tests don't start until onPrepare is complete

I want onPrepare to finish before any tests are run and I'm using async / await.
I'm new to javascript and protractor but I've been writing test automation for a couple of decades. This seems like an incredibly simple thing to want to do, have onPrepare finish before a test starts, but I'm confused my everything I've seen out there.
I've set SELENIUM_PROMISE_MANAGER: false so I don't want to use promises to do this, right?
My landing page in anguler
do I use async and await on onPrepare or browser.driver.wait or webdriver.until.elementLocated? If so, do I put 'await' before those waits? (That seems redundant)
onPrepare: async() => {
await browser.driver.get( 'https://localhost:8443/support-tool/#/service/config');
await browser.driver.findElements(by.className('mat-table'));
browser.driver.wait(webdriver.until.elementLocated(by.css('mat-select-value')), 10000);//(Returns webdriver not defined)
},
first, I get webdriver not defined when I run it. Once I get it to work, will my tests wait for onPrepare to be completed before they start running?
So Protractor is a wrapper for the webdriverJS and webdriverJS is completely asynchronous. To give a very high level definition, Protractor wraps webdriverJS up so that every action returns a promise (for instance .get(), .sendKeys(), .findElement()).
Previously webdriverJS had what is referred to as the 'control flow' which allowed users to write code as they would in any synchronous programming language and the fact the almost everything is a promise was handled behind the scenes. This feature has been deprecated in the latest versions and the main reason is that the introduction of the async/await style of handling promises makes it much easier for users to manage promises ourselves.
If you are using protractor 6.0+ the control flow is disabled by default but it will be disabled for you regardless as you have you have set SELENIUM_PROMISE_MANAGER: false. You will need to manually manage your promises, which you are doing, by using async/await.
browser.driver vs browser
I also want to point out the by using browser.driver.get you are referring to the underlying selenium instance and not the protractor wrapper instance therefore it will not wait for the angular page to stabilize before interacting (I could be mistaken on this). There is more info on the distinction in this thread.
Any action that involves the browser or the file system will likely be a promise so include the await before them and any function that contains an await needs to be declared async.
I would write your code as follows:
onPrepare: async() => {
await browser.get('https://localhost:8443/support-tool/#/service/config');
let someElement = await element(by.css('mat-select-value'));
await browser.wait(protractor.ExpectedConditions.presenceOf(someElement), 10000);
},
Finally, as long is your onPrepare is using awaits properly it should for sure complete before your tests begin.
Hope that helps and is clear, it was longer than I anticipated.

Why stub in cypress may not work for route called after page was loaded

I am using cypress to write tests and have a problem which doesn't appear in every test. In some cases it works and I don't know why. So...
The Problem:
I defined a route and alias for it in beforeEach:
beforeEach(function () {
cy.server()
cy.route('GET', '/favourites?funcName=columnPreset', []).as('columnPresetEmpty')
cy.visit('#/search')
})
Stub works fine if http request occured on page load.
But if I perform request responding to click event (modal dialog opens and executes http request) it just appear in commands not makred as stubbed and following cy.wait('#columnPresetEmpty') fails with request timeout.
it('does not work', function () {
cy.get('[data-test=button-gridSettings]').click()
cy.wait('#columnPresetEmpty')
})
At the same time in other tests I have almost similar functionality where request is performed just by clicking on a button, without opening new modal window. It's the only difference.
What am I doing wrong?
The issue might be cypress can not yet fully handle fetch calls. You can disable it the following way but make sure you have fetch polyfill. This will then issue XHR requests which cypress can observe.
cy.visit('#/search', {
onBeforeLoad: (win) => {
win.fetch = null
}
})
More to read here:
https://github.com/cypress-io/cypress/issues/95#issuecomment-281273126
I found the reason causing such behavior. Problem was not in a modal window itself, but code performing second request was called in promise's callback of another request. Something like:
fetch('/initData')
.then(loadView)
And loadView function executed second fetch.
So when I removed loadView from promise's callback both requests become visible for cypress.
For info, I tried it out on my search modal (in a Vue app) and it works ok.
What I did:
created a dummy file named test-get-in-modal.txt in the app's static folder
added an http.get('test-get-in-modal.txt') inside the modal code, so it only runs after the modal is open
in the spec, did a cy.server(), cy.route('GET', 'test-get-in-modal.txt', []).as('testGetInModal') in a before()
in the it() added cy.wait('#testGetInModal') which succeeded
changed to cy.route('GET', 'not-the-file-you-are-looking-for.txt'..., which failed as expected
The only difference I can see is that I cy.visit() the page prior to cy.server(), which is not the documented pattern but seems to be ok in this scenario.

Log Javascript console output in Laravel Dusk

I am using Laravel 5.6 and Laravel Dusk 3.0.9.
Dusk is pretty handy, but when a test fails on a page where there is some Javascript functionality it can be pretty hard to work out what went wrong. Dusk generates a screenshot, which helps, but what I really need is to see the output from the Javascript console.
Apparently this is possible - Dusk creates a tests/Browser/console directory as part of its installation, and this PR suggests the JS console output is logged, or at least loggable.
There is no documentation (that I can find) about how to actually do that though. Browsing the diffs in that PR, I see a new method logConsole() (later renamed to storeConsoleLog() as #Jonas pointed out in the comments), but I can't seem to get it to do anything, eg:
$browser->visit('/somewhere')
->select('#foo', '2')
->value('#date', '2018-07-29')
->storeConsoleLog('bar')
->assertEnabled('button[type=submit]');
This test fails, and generates a nice screenshot, but there is no sign of any logfile. I've tried moving the position of ->storeConsoleLog('bar') around in the chain, eg first or last, and as a separate line before or after the chain:
$browser->visit('/somewhere')
->...full chain here;
$browser->storeConsoleLog('bar');
But none of them make any difference. My JS has a series of console.log()s which I use when testing myself in a browser, and which will tell me exactly what went wrong. I was expecting this functionality to log those messages.
Am I misunderstanding that PR, is this even possible? If so, how?
UPDATE
By debugging the storeConsoleLog() method in vendor/laravel/dusk/src/Browser.php I can see that the method is being called correctly, but there is no console content to log. If I manually repeat the steps the test is taking in Chrome, there are lines being written to Chrome devtools console, in fact 3 are written on page load. Why can't Dusk see those?
UPDATE 2
I found that if you remove '--headless' from the driver() method in DuskTestCase, the browser will be displayed during tests. You can then display the dev tools for that browser, and watch the console output live as the tests run. It is too fast to really be useful, and if there is an error the browser closes and you lose whatever was on the console anyway (unless there's a way to leave the browser open on failure?), but adding this here in case it is useful to someone!
There is apparently no way to get non-error console output from the browser.
There are other log types, but Chrome only supports browser (used by Dusk) and driver.
You'll probably have to use alerts. But screenshots don't contain alerts, so you have to get the text:
$browser->driver->switchTo()->alert()->getText()
You could also use something like document.write() and check the output on the screenshot.
It is possible. There's an answer here written in Python from 2014 that shows it's been possible since at least that long.
Override \Laravel\Dusk\TestCase::driver:
protected function driver()
{
$desired_capabilities = new DesiredCapabilities([
'browserName' => 'chrome',
'platform' => 'ANY',
]);
// the proper capability to set to get ALL logs stored
$desired_capabilities->setCapability('loggingPrefs', [
'browser' => 'ALL',
'driver' => 'ALL',
]);
// return the driver
return RemoteWebDriver::create(
'http://localhost:9515',
$desired_capabilities
);
}

Service worker: check cached resources are up-to-date or latest

I've sw.js code like:
self.addEventListener('install', e => {
e.waitUntil(
caches.open('cache').then(cache => {
return cache.addAll([
'/',
'/index.html',
'/styles/main.css',
'/scripts/main.min.js'
])
.then(() => self.skipWaiting());
})
)
});
self.addEventListener('activate', event => {
event.waitUntil(self.clients.claim());
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request);
})
);
});
I've updated the index.html file recently, but the file isn't reflected the site. It's still showing the old content of index.html.
Now, How can I make sure app will be serving the latest code if file's changed on the server?
I've been seeing some related answers (like update and refresh), but please tell me what's going on each part of the answer.
You have two options:
Switch from your current cache-first strategy in your fetch handler to a strategy like stale-while-revalidate.
Use a build-time tool to generate a service worker that changes each time one of your local resources changes, which will trigger the install and activate handlers, giving them a chance to update your users' caches with the latest copies of your assets. These tools work by generating hashes of each local file and inlining those hashes into the generated service worker script.
Option 2. is more efficient, because your users will only have to make requests for your assets when something actually changes, as opposed to when using a stale-while-revalidate strategy, which requires firing off a "revalidate" request in the background each time your fetch handler is invoked. Option 2. does require a bit more work, though, because you need to incorporate a step into your build process to generate your service worker for you.
If you go the option 2. route, there are a few different build tools that I know about:
sw-precache (disclosure: I'm the author)
offline-plugin

Resources