I'm trying to take a simple screenshot using Cypress, however, it only works in the Cypress GUI (cypress open).
Whenever I run cypress run, it shows that the test succeeded but there's not screenshot saved.
This my code:
describe('Snapshot', () =>
it.only('Take snapshot', async () => {
cy.visit("http://www.google.com");
cy.screenshot();
});
});
Actually, even in the GUI you may get inconsistent behaviour with the code you've provided: you should try to refactor your tests to avoid the use of async.
Cypress already does a lot of async stuff itself. See this answer for more information about how cypress uses promises, and avoiding async, as well as a few things you could try if you really want to use async.
Your code will successfully create screenshots under cypress run if you remove the async keyword like this:
describe('Snapshot', () => {
it.only('Take snapshot', () => {
cy.visit("http://www.google.com");
cy.screenshot();
});
});
Note: I also added the missing opening brace { at the end of the first line. Since you say this code ran for you in the GUI, I'm assuming the brace was not the issue but was simply lost while posting.
Related
Protractor hangs completely when trying to get any element property after logging in (idk if it's related to logging in or related just to switching pages).
it("Should get location of main container", async function() {
await LoginPage.validLogin();
// Works and logs in the dashboard
await browser.sleep(3000);
// Get the main container by class name
const container = await element(by.css(".main-container"));
// Logs properly the element functions (as expected)
console.log(container);
console.log(await container.getLocation()); // Hangs here
});
In this case, I'm trying to get the location of the main container element on the page. The first console.log fires and shows properly, while the second hangs completely, so I get the script timeout. Increasing the timeout time doesn't help at all...
I found online that misusing $timeout in AngularJS instead of using $interval may lead to this strange behaviour, but I really can't skim through the entire (very big!) project's codebase to change everything hoping that it just works, not to talk about the external libraries using $timeout.
I have SELENIUM_PROMISE_MANAGER = false; in my Protractor config so I disabled the built-in Control Flow in order to manually manage the promises using async/await, but even if I use the built-in Control Flow without using async/await I get the very same behaviour and error.
I'm using Jasmine as testing framework.
Maybe I'm missing something? Any help would be much appreciated, thanks!
This is caused by the fact that angular is not stable. Have a look at the link below. I found my answer there. When the page you are trying to test is open go to the browser dev tools and type in the console getAllAngularTestabilities(). There are a few properties here that indicate whether angular is ready to be tested. hasPendingMicrotasts needs to be false. hasPendingMacroTasks needs to be false. isStable needs to be true. I put a screenshot below. In my screenshot hasPendingMacrotasks is true and it must be false. So the page I looked at was not ready to be tested.
Failed: script timeout: result was not received in 11 seconds From: Task: Protractor.waitForAngular() - Locator: By(css selector, #my-btn)
Try something like this:
it("Should get location of main container", async function() {
await LoginPage.validLogin();
const container = await element(by.css(".main-container"));
await browser.wait(protractor.ExpectedConditions.presenceOf(container), 5000, 'Element taking too long to appear in the DOM');
await console.log(await container.getLocation());
});
I don't think that getLocation() exists in the Javascript bindings for selenium. I couldn't find it in the source code anyway. So that promise will never return which is why it hangs. But I the you can achieve basically the same thing with getRect():
it("Should get location of main container", async function() {
await LoginPage.validLogin();
const container = await element(by.css(".main-container"));
await browser.wait(protractor.ExpectedConditions.presenceOf(container), 5000, 'Element taking too long to appear in the DOM');
await console.log(await container.getRect());
});
I have an application that I created using Ext JS and I am writing tests for it using Selenium WebDriver (the Node package - version 4.0.0-alpha.1) and Jest. In one of my test scripts, I want to wait for a function to be called before continuing with the remaining test logic but I am not sure how to implement this. To help demonstrate what I am trying to accomplish, I created a sample app using Sencha Fiddle. All of the code for that app as well as a running version of the app can be found here: https://fiddle.sencha.com/#view/editor&fiddle/2o6m. If you look in the app folder of the fiddle, you'll see that there is a Test component with a simple controller. There is an onAdd function in the controller and that is the function I want to wait for before continuing since in my actual application there is code in that function that the rest of the tests are dependent on. I can access that function in dev tools by running the following line of code: Ext.ComponentQuery.query('test')[0].getController().onAdd (note that the activeElement needs to be set to the preview iFrame (document.getElementsByName('fiddle-run-iframe')[0]) in the fiddle for this to work). This means I can access the function in driver.executeScript the same way, but once I have the function, I am not sure how to wait for it to be called before continuing. I was trying to use the mock/spy feature in Jest, but this is not working because jest is not defined inside driver.executeScript so I can't call jest.fn or jest.spyOn. I created a sample test script that works with the sample app to demonstrate what I am trying to do, but right now it fails with an error since, as I said, jest is not defined inside driver.executeScript. Here is the code for the sample test script:
const {Builder, By, Key, until} = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');
const driver = global.driver = new Builder()
.forBrowser('chrome')
.setChromeOptions(new chrome.Options())
.build();
jest.setTimeout(10000);
beforeAll(async () => {
await driver.manage().window().maximize();
await driver.get('https://fiddle.sencha.com/#view/editor&fiddle/2o6m');
});
afterAll(async () => {
await driver.quit();
});
describe('check title', () => {
it('should be SAMPLE STORE LOAD', async () => {
expect(await driver.wait(until.elementLocated(By.css('.fiddle-title'))).getText()).toBe('SAMPLE STORE LOAD');
});
});
describe('check store add', () => {
it('should call add function', async () => {
let spy;
await driver.switchTo().frame(await driver.findElement(By.name('fiddle-run-iframe')));
await driver.wait(until.elementIsNotVisible(await driver.wait(until.elementLocated(By.xpath('//div[starts-with(#id, "loadmask")]')))));
await driver.executeScript(() => {
const test = document.getElementsByName('fiddle-run-iframe')[0].contentWindow.Ext.ComponentQuery.query('test')[0];
spy = jest.spyOn(test.getController(), 'onAdd'); //This throws an error since jest is not defined inside driver.executeScript
});
expect(spy).toHaveBeenCalled(); //wait for onAdd function to be called before continuing
});
//additional tests occur here after wait...
});
You can ignore all the logic related to switching to the iFrame because that is only necessary for the fiddle since it runs the preview of the app in an iFrame. My actual app does not exist inside an iFrame. Nonetheless, I think this script effectively demonstrates what I am trying to accomplish which is to wait until the onAdd function is called before continuing with my tests. I am not sure if I need to use Selenium or Jest, some combination of the two, or a different testing tool entirely to do this. I am relatively new to writing tests and this is my first time posting on Stack Overflow so I apologize if anything I said is unclear. I would be happy to clarify anything if you have any questions and grateful for any advice you have to offer!
From my point of view, combining 2 different frameworks (approaches to testing) in a single test might not be the best idea. Here are some thoughts on how I would deal with a similar case. Very little theory:
Selenium - is great in simulation end-user behavior with a browser. Basically, it can simulate actions (clicks, text inputs, etc.) and can get information from a browser window (presence of elements, texts, styles, etc.)
Jest - is a unit test framework to jest javascript code.
executeScript is a method of Selenium, it executes any javascript code in a browser. Browser does not know anything about jest - so it's expected that you faced an error you described. Your project knows about jest, as it's specifically imported in a project.
Back to the question "How to check in Selenium that a js function has been called?"
The most typical solution - is to wait until something has changed on a browser screen. Typical cases:
some element appears
some element disappears
some element's style changed
wait for condition in js example
(a bit of a hack) Another possible solution is to add a some flag in the js code of an app that will be false before function is called and will be set to true after function is called. Then you can access this flag's value in the executeScript.
some possible implementations here
tl;dr: When I run my test case, steps executed seem to work, but the test bails out early on a failure to find an element that hasn't even loaded yet. It seems like the waits I have around locating certain elements are loaded/launched as soon as the test is launched, not when the lines should actually be executed in the test case. I think this is happening because the page is barely (correctly) loaded before the "search" for the element to verify the page has loaded bails out. How do I wrangle the event loop?
This is probably a promise question, which is fine, but I don't understand what's going on. How do I implement my below code to work as expected? I'm working on creating automated E2E test cases using Jasmine2 and Protractor 5.3.0 in an Angular2 web app.
describe('hardware sets', () => {
it('TC3780:My_Test', async function() {
const testLogger = new CustomLogger('TC3780');
const PROJECT_ID = '65';
// Test Setup
browser.waitForAngularEnabled(false); // due to nature of angular project, the app never leaves zones, leaving a macrotask constantly running, thus protractor's niceness with angular is not working on our web app
// Navigate via URL to planviewer page for PROJECT_ID
await planListingPage.navigateTo(PROJECT_ID); // go to listing page for particular project
await planListingPage.clickIntoFirstRowPlans(); // go to first plan on listing page
await planViewerPage.clickOnSetItem('100'); // click on item id 100 in the plan
});
});
planViewerPage.po.ts function:
clickOnSetItem(id: string) {
element(by.id(id)).click();
browser.wait(until.visibilityOf(element(by.css('app-side-bar .card .info-content'))), 30000); // verify element I want to verify is present and visible
return expect(element(by.css('app-side-bar .card .info-content')).getText).toEqual(id); //Verify values match, This line specifically is failing.
}
This is the test case so far. I need more verification, but it is mostly done. I switched to using async function and awaits instead of the typical (done) and '.then(()=>{' statement chaining because I prefer not having to do a bunch of nesting to get things to execute in the right order. I come from a java background, so this insanity of having to force things to run in the order you write them is a bit much for me sometimes. I've been pointed to information like Mozilla's on event loop, but this line just confuses me more:
whenever a function runs, it cannot be pre-empted and will run entirely before any other code
runs (and can modify data the function manipulates).
Thus, why does it seem like test case is pre-evaluated and the timer's set off before any of the pages have been clicked on/loaded? I've implemented the solution here: tell Protractor to wait for the page before executing expect pretty much verbatim and it still doesn't wait.
Bonus question: Is there a way to output the event-loop's expected event execution and timestamps? Maybe then I could understand what it's doing.
The behavior
The code in your function is running asynchronously
clickOnSetItem(id: string) {
element(by.id(id)).click().then(function(){
return browser.wait(until.visibilityOf(element(by.css('app-side-bar .card .info-content'))), 30000);
}).then(function(){
expect(element(by.css('app-side-bar .card .info-content')).getText).toEqual(id);
}).catch(function(err){
console.log('Error: ' + err);
})
}
I'm trying to understand how The WebDriver Control Flow works exactly.
According to the linked documentation (https://github.com/angular/protractor/blob/master/docs/control-flow.md) no callback method / call is needed in jasmine:
Protractor adapts Jasmine so that each spec automatically waits until the control flow is empty before exiting.
However, I have to use cucumber. I'm using the library protractor-cucumber-framework as described here: https://github.com/angular/protractor/blob/master/docs/frameworks.md#using-cucumber
It works well, but for some reason it works better when I skip the callback variable then when I try using it. For instance, this code fails:
this.Given(/^the login page is active$/, function (callback) {
browser.get('/').then(callback);
});
With the error ...
TypeError: text.split is not a function
[launcher] Process exited with error code 1
On the other hand, this codes works as I want it to work and cucumber / protractor seems to be waiting until the page is loaded, before executing further functions:
me.Given(/^the login page is active$/, function () {
browser.get('/');
});
But I couldn't find any documentation confirming that I really can omit the callback function.
Currently the page I tried to test doesn't use Angular and therefore I have the following code in my config file:
onPrepare: function() {
browser.ignoreSynchronization = true;
}
Protractor uses WebDriverJS underneath. And WebDriverJS uses a promise manager where it queues its commands. Here is some excerpts from their wiki page here
Internally, the promise manager maintains a call stack. Upon each turn
of the manager's execution loop, it will pull a task to execute from
the queue of the top-most frame. Any commands scheduled within the
callback of a previous command will be scheduled in a new frame,
ensuring they run before any tasks previously scheduled. The end
result is that if your test is written-in line, with all callbacks
defined by function literals, commands should execute in the order
they are read vertically on the screen. For example, consider the
following WebDriverJS test case:
driver.get(MY_APP_URL);
driver.getTitle().then(function(title) {
if (title === 'Login page') {
driver.findElement(webdriver.By.id('user')).sendKeys('bugs');
driver.findElement(webdriver.By.id('pw')).sendKeys('bunny');
driver.findElement(webdriver.By.id('login')).click();
}
});
driver.findElement(webdriver.By.id('userPreferences')).click();
This test case could be rewritten using !WebDriver's Java API as follows:
driver.get(MY_APP_URL);
if ("Login Page".equals(driver.getTitle())) {
driver.findElement(By.id("user")).sendKeys("bugs");
driver.findElement(By.id("pw")).sendKeys("bunny");
driver.findElement(By.id("login")).click();
}
driver.findElement(By.id("userPreferences")).click();
Now going back to your question, since you are omitting callback from your steps, cucumber is treating your test code as synchronous. See documentation here. And because the way protractor/WebdriverJS handles promise manager the way described above, everything works as expected for you.
As far as the error you are getting when using callback, I'm not sure. I do it exactly the same way you are doing. See here. I'm using cucumber ^0.9.2. It could be that your cucumber version has issues.
On a side note, I found that you could return promises instead of using callbacks to let cucumber know that you are done executing. So something like this works as well (assuming you are using ^0.9.2). I tested it,
me.Given(/^the login page is active$/, function () {
return browser.get('/');
});
I have a following function:
function prompt_mandatory_field_completion(){
$("#mandatory_fail").show(150, function() {
setTimeout(function() {
$("#mandatory_fail").fadeOut(500)
}, 2000);
});
window.scrollTo(0,0)
}
That I would like to test with jasmine but regardless to what I put in my spec file the test seems to pass.
The spec file contains the following code :
it(' NEED TO FIX THAT FADE OUT Should prompt user to fill in mandatory questions via prompt_mandatory_field_completion function', function() {
prompt_mandatory_field_completion();
setTimeout(2000, function(){
expect($('#mandatory_fail').css('display').toEqual('random thing'));
});
In my SpecRunner.html I am using the following function that I run in before each test in this description block:
function setupFixtures(){
setFixtures('<div id="mandatory_fail" style="display:none;"></div>');
prompt_mandatory_field_completion();
};
Any idea how to make this into a meaningful test? I guess I have been staring at it way too long and poking it from all the directions.
Best,
Adam
You're trying to write a functional test of asynchronous behavior. You might have a lot better experience trying to use protractor for this sort of test. It's tuned more toward asserting things that will eventually be true.
However, jasmine does have an asynchronous facility since about 2.0, known as done(), that will insist that all of the asynchronous code has run before the test passes or fails.
You have to pass the done function to get asynchronous tests :
it(' NEED TO FIX THAT FADE OUT Should prompt user to fill in mandatory questions via prompt_mandatory_field_completion function', function(done) {
prompt_mandatory_field_completion();
setTimeout(2000, function(){
expect($('#mandatory_fail').css('display').toEqual('random thing'));
done();
});
}, 3000);
You can also pass a timeout as a last parameter, depending on what you've set in your jasmine's settings.
Otherwise, Jasmine will consider this test to fail if its execution exceed its timeout.