I have an anchor in an Angular app which is disabled until the page content loads, which takes some time.
Is there any way to click it only after it stops being disabled?
I tried with:
const myItems = cy.get('whatever-selectors');
myItems.contains('some text', {timeout: 10000}).should('not.be.disabled').click();
This doesn't work.
The documentation states that Cypress will retry clicking until the should condition is fulfilled or the timeout is exceeded, but it fails after less than half the time.
Any suggestions?
You should apply the timeout: 10000 with cy.get()
cy.get('whatever-selectors', {
timeout: 20000
}).should('be.enabled').click();
The problem here is that ,,should" is an assertion. To trigger a click event, cypress has to find an element first.
For this case usually worth to use a pseudo selector like
cy.get('whatever-selectors:not([disabled])').click();
In case of the loading takes more time than the default configuration you can alter the line like
cy.get('whatever-selectors:not([disabled])', { timeout: 10000 }).click();
Related
I have made a custom command for logout which I am calling in my afterEach hook but every time I am getting this error. Below is the code attached:
Cypress.Commands.add('logout', () => {
//cy.get('#react-burger-menu-btn').should('be.visible').click({force:true})
cy.xpath("//div[#class = 'bm-burger-button']").click({ force: true })
cy.get('#react-burger-menu-btn')
cy.get('#logout_sidebar_link').click()})
I am using the Swag Labs dummy website. Attaching the website's link as well for reference:
https://www.saucedemo.com/
Took help from the documentation but unable to solve the issue.
You can try waiting for visibility of all the elements involved.
cy.get('#react-burger-menu-btn').should('be.visible')
cy.get('#logout_sidebar_link').should('be.visible')
.click()
If it does not work, look at the HTML of the menu and try checking other elements for visibility as well.
You should open the devtools and observe which elements are modified after the click (they will flash in the devtools Elements tab).
Cypress.Commands.add('logout', () => {
cy.get('button[id="react-burger-menu-btn"]').click()
cy.get('#logout_sidebar_link').click()
});
Try this
I have done some reading and investigation on this error, but not sure what the correct answer is for my situation. I understand that in dev mode, change detection runs twice, but I am reluctant to use enableProdMode() to mask the issue.
Here is a simple example where the number of cells in the table should increase as the width of the div expands. (Note that the width of the div is not a function of just the screen width, so #Media cannot easily be applied)
My HTML looks as follows (widget.template.html):
<div #widgetParentDiv class="Content">
<p>Sample widget</p>
<table><tr>
<td>Value1</td>
<td *ngIf="widgetParentDiv.clientWidth>350">Value2</td>
<td *ngIf="widgetParentDiv.clientWidth>700">Value3</td>
</tr></table>
This on its own does nothing. I'm guessing this is because nothing is causing change detection to occur. However, when I change the first line to the following, and create an empty function to receive the call, it starts working, but occasionally I get the 'Expression has changed after it was checked error'
<div #widgetParentDiv class="Content">
gets replaced with
<div #widgetParentDiv (window:resize)=parentResize(10) class="Content">
My best guess is that with this modification, change detection is triggered and everything starts responding, however, when the width changes rapidly the exception is thrown because the previous iteration of change detection took longer to complete than changing the width of the div.
Is there a better approach to triggering the change detection?
Should I be capturing the resize event through a function to ensure
change detection occurs?
Is using #widthParentDiv to access the
width of the div acceptable?
Is there a better overall solution?
For more details on my project please see this similar question.
Thanks
To solve your issue, you simply need to get and store the size of the div in a component property after each resize event, and use that property in the template. This way, the value will stay constant when the 2nd round of change detection runs in dev mode.
I also recommend using #HostListener rather than adding (window:resize) to your template. We'll use #ViewChild to get a reference to the div. And we'll use lifecycle hook ngAfterViewInit() to set the initial value.
import {Component, ViewChild, HostListener} from '#angular/core';
#Component({
selector: 'my-app',
template: `<div #widgetParentDiv class="Content">
<p>Sample widget</p>
<table><tr>
<td>Value1</td>
<td *ngIf="divWidth > 350">Value2</td>
<td *ngIf="divWidth > 700">Value3</td>
</tr>
</table>`,
})
export class AppComponent {
divWidth = 0;
#ViewChild('widgetParentDiv') parentDiv:ElementRef;
#HostListener('window:resize') onResize() {
// guard against resize before view is rendered
if(this.parentDiv) {
this.divWidth = this.parentDiv.nativeElement.clientWidth;
}
}
ngAfterViewInit() {
this.divWidth = this.parentDiv.nativeElement.clientWidth;
}
}
Too bad that doesn't work. We get
Expression has changed after it was checked. Previous value: 'false'. Current value: 'true'.
The error is complaining about our NgIf expressions -- the first time it runs, divWidth is 0, then ngAfterViewInit() runs and changes the value to something other than 0, then the 2nd round of change detection runs (in dev mode). Thankfully, there is an easy/known solution, and this is a one-time only issue, not a continuing issue like in the OP:
ngAfterViewInit() {
// wait a tick to avoid one-time devMode
// unidirectional-data-flow-violation error
setTimeout(_ => this.divWidth = this.parentDiv.nativeElement.clientWidth);
}
Note that this technique, of waiting one tick is documented here: https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#parent-to-view-child
Often, in ngAfterViewInit() and ngAfterViewChecked() we'll need to employ the setTimeout() trick because these methods are called after the component's view is composed.
Here's a working plunker.
We can make this better. I think we should throttle the resize events such that Angular change detection only runs, say, every 100-250ms, rather then every time a resize event occurs. This should prevent the app from getting sluggish when the user is resizing the window, because right now, every resize event causes change detection to run (twice in dev mode). You can verify this by adding the following method to the previous plunker:
ngDoCheck() {
console.log('change detection');
}
Observables can easily throttle events, so instead of using #HostListener to bind to the resize event, we'll create an observable:
Observable.fromEvent(window, 'resize')
.throttleTime(200)
.subscribe(_ => this.divWidth = this.parentDiv.nativeElement.clientWidth );
This works, but... while experimenting with that, I discovered something very interesting... even though we throttle the resize event, Angular change detection still runs every time there is a resize event. I.e., the throttling does not affect how often change detection runs. (Tobias Bosch confirmed this:
https://github.com/angular/angular/issues/1773#issuecomment-102078250.)
I only want change detection to run if the event passes the throttle time. And I only need change detection to run on this component. The solution is to create the observable outside the Angular zone, then manually call change detection inside the subscription callback:
constructor(private ngzone: NgZone, private cdref: ChangeDetectorRef) {}
ngAfterViewInit() {
// set initial value, but wait a tick to avoid one-time devMode
// unidirectional-data-flow-violation error
setTimeout(_ => this.divWidth = this.parentDiv.nativeElement.clientWidth);
this.ngzone.runOutsideAngular( () =>
Observable.fromEvent(window, 'resize')
.throttleTime(200)
.subscribe(_ => {
this.divWidth = this.parentDiv.nativeElement.clientWidth;
this.cdref.detectChanges();
})
);
}
Here's a working plunker.
In the plunker I added a counter that I increment every change detection cycle using lifecycle hook ngDoCheck(). You can see that this method is not being called – the counter value does not change on resize events.
detectChanges() will run change detection on this component and its children. If you would rather run change detection from the root component (i.e., run a full change detection check) then use ApplicationRef.tick() instead (this is commented out in the plunker). Note that tick() will cause ngDoCheck() to be called.
This is a great question. I spent a lot of time trying out different solutions and I learned a lot. Thank you for posting this question.
Other way that i used to resolve this:
import { Component, ChangeDetectorRef } from '#angular/core';
#Component({
selector: 'your-seelctor',
template: 'your-template',
})
export class YourComponent{
constructor(public cdRef:ChangeDetectorRef) { }
ngAfterViewInit() {
this.cdRef.detectChanges();
}
}
Simply use
setTimeout(() => {
//Your expression to change if state
});
The best solution is to use setTimeout or delay on the services.
https://blog.angular-university.io/angular-debugging/
Mark Rajcok gave a great answer. The simpler version (without throttling) would be:
ngAfterViewInit(): void {
this.windowResizeSubscription = fromEvent(window, 'resize').subscribe(() => this.onResize())
this.onResize() // to initialize before any change
}
onResize() {
this.width = this.elementRef.nativeElement.getBoundingClientRect().width;
this.changeDetector.detectChanges();
}
I have a directive that produces the following html structure:
<div class="popover ng-isolate-scope" ng-mouseover="toggle(true)" ng-mouseleave="toggle(false)" popover="" label="hover time!" trigger-class="button" content-class="specialContentClass">
<span id="thing" class="popover-trigger button">hover time!</span>
<div ng-transclude="" ng-show="show" class="popover-content ng-hide">
<div class="ng-scope">Popover content </div>
</div>
</div>
The code works fine and the popover content is correctly shown when you mouseover manually using a browser.
I'm trying to test the mouseover functionality with the following protractor test:
it('should display the popover-content on mouseover', function() {
browser.get('http://localhost:9000/');
browser.actions()
.mouseMove(element(by.css('.popover')).find()).perform();
expect(element(by.css('.popover-content'))
.isDisplayed().toBeTruthy());
});
The test seems to run, the browser opens but I don't see the popup-content displaying before the browser then closes so I'm fairly sure the mousemove bit isn't working for some reason. The following is then output in the terminal:
launcher] 0 instance(s) of WebDriver still running
[launcher] chrome #1 failed 1 test(s)
[launcher] overall: 1 failed spec(s)
[launcher] Process exited with error code 1
ycompu:angular ycompu$
I've read the documentation and using browser is definitely the right way to approach this test. I'm at a loss as the syntax looks correct to me.
One possible problem is that you need to make it wait for angular to load:
it('should display the popover-content on mouseover', function() {
browser.get('http://localhost:9000/');
browser.waitForAngular();
browser.actions().mouseMove(element(by.css('.popover'))).perform();
expect(element(by.css('.popover-content')).isDisplayed()).toBeTruthy();
});
I've also removed the find() call (not sure if you really need it here) and fixed the parenthesis closing order in the last line.
I sort of discovered a workaround to the mouse hover issue on chrome by accident. If we chain the mouseMove() method twice , it works.
Code that doesn't work on chrome:
browser.actions.mouseMove(element).click().perform();
Code with workaround(which works):
browser.actions.mouseMove(element).mouseMove(element).click().perform();
For none angular sites , please try the below code.The code has been tested and passed
in protractor --version 5.4.2 with Chrome 79 the latest as per today .
describe('My first test class', function() {
it('My function', function() {
browser.driver.ignoreSynchronization = true;// for non-angular set true. default value is false
browser.waitForAngularEnabled(false);
browser.driver.get('http://demoqa.com/menu/');
//var menuElectronics= element(by.id('ui-id-4'));//We can define an element and move to it
//browser.actions().mouseMove(menuElectronics).perform();
//Directly find the element using id
browser.actions().mouseMove(element(by.id('ui-id-4'))).perform();
//Click on the element that appeared after hover over the electronics
element(by.id('ui-id-7')).click();
});
})
Use browser.waitForAngular() before calling browser.actions().mouseMove("mouseoverelement").perform();... because you need to wait till angular load.
it('mouseover test', function() {
....
....
browser.waitForAngular();
browser.actions().mouseMove(element(by.css('#mouseoverelement'))).perform();
expect(element(by.css('#mouseoverelement')).isDisplayed()).toBeTruthy();
});
use this method pass the locater to the method this is working fine
mouseHover: function (locator) {
return browser.actions().mouseMove(locator).perform();
},
I keep getting the error TurnJsError: This selector has more than 1 element i have made a few checks in Firebug and i know that my Jquery is being loaded as are all my libraries/scripts i need.
The error appears after my page has loaded, if i add the following part of my javascript to the console and run it before the page has fully loaded then its ok but still returns the same error as above, however if i let the page load (get the error from above again) and then run that script in console i get another error TypeError: Argument 1 of Node.insertBefore does not implement interface Node.:
function loadApp() {
// Create the flipbook
$('.flipbook').turn({
// Width
width:922,
// Height
height:600,
// Elevation
elevation: 50,
// Enable gradients
gradients: true,
// Auto center this flipbook
autoCenter: true
});
}
// Load the HTML4 version if there's not CSS transform
$(document).ready(function() {
yepnope({
test : Modernizr.csstransforms,
yep: ['../../lib/turn.js'],
nope: ['../../lib/turn.html4.min.js'],
both: ['css/basic.css'],
complete: loadApp
});
});
Could i be missing something thats the cause for throwing these errors?
when i add .first() so its like $('.flipbook').first().turn({ after the page has fully loaded i get slight movement in my image, but still getting the error TypeError: Argument 1 of Node.insertBefore does not implement interface Node. When i click on my image it dissapears but the next image is not displayed instead i get another error TypeError: c is null
turn.js work with a single element. Use the .each function.
$('.flipbook').each(function(){
$(this).turn(...);
})
Note that turn may need a unique id on the container.
I need to be able to change the filebrowserUploadUrl of CKEditor when I change some details on the page, as the querystring I pass through is used by the custom upload process I've put in place.
I'm using the JQuery plugin. Here's my code:
$('#Content').ckeditor({
extraPlugins: 'autogrow',
autoGrow_maxHeight: 400,
removePlugins: 'resize'
});
$("#Content").ckeditorGet().on("instanceReady", function () {
this.on("focus", function () {
// Define browser Url from selected fields
this.config.filebrowserUploadUrl = filebrowserUploadUrl: '/my-path-to-upload-script/?ID1=' + $("ID1").val() + '&ID2=' + $("#ID2").val();
});
});
This works fine the first time, but if I come out of the dialogue and change the value of #ID1 and #ID2, it keeps the previous values. When I debug, the filebrowserUploadUrl is set correctly, but it doesn't affect the submission values. It seems the config values are cached.
Is there any way to change a config value on the fly?
Currently I don't see any possibility to change this URL on the fly without hacking.
Take a look at http://dev.ckeditor.com/browser/CKEditor/trunk/_source/plugins/filebrowser/plugin.js#L306
This element.filebrowser.url property is set once and as you can see few lines above it will be reused again. You can try to somehow find this element and reset this property, but not having deeper understanding of the code of this plugin I don't know how.
Second option would be to change this line #L284 to:
url = undefined;
However, I haven't check if this is the correct solution :) Good luck!
BTW. Feel free to fill an issue on http://dev.ckeditor.com.
I solved this by reloading the editor whenever a change occurred; I actually went through the source code for the browser plugin etc, but couldn't get any changes to work (and of course, I really didn't want to change anything for future upgrades).
function setFileBrowserUrl() {
// Remove editor instance
$("#Content").ckeditorGet().destroy();
// Recreate editor instance (needed to reset the file browser url)
createEditor();
}
function createEditor() {
$('#Content').ckeditor({
filebrowserUploadUrl: '/my-path-to-upload-script/?ID1=' + $("ID1").val() + '&ID2=' + $("#ID2").val(),
extraPlugins: 'autogrow',
autoGrow_maxHeight: 400,
removePlugins: 'resize'
});
}
Then I call setFileBrowserUrl every time the relevant elements on the page change. Not ideal, but it works for my purposes :)