Why does Cypress not recognise the testid of a react-datepicker element? - reactjs

Target
To make Cypress recognise and manipulate a DatePicker element.
Problem
I cannot make Cypress recognise the data-testid of a DatePicker element, and so have no way of testing it E2E.
Error
Timed out retrying: Expected to find element: [data-testid="edit_dob"], but never found it.
Attempts
I have tried placing the DatePicker element inside input and div elements to target instead, but they fail as expected for other reasons.
I have also looked through the docs for both DatePicker and Cypress for mention of the other, and for similar questions here.
Code
The code below works as expected, it's just Cypress that seems to be incompatible with the DatePicker input.
function displayEditDetails() {
let listOfForms = [];
for (const detail in details) {
listOfForms.push(
<FormGroup controlId={detail} key={`key_edit_${detail}`}>
<FormLabel>{detail.charAt(0).toUpperCase() + detail.slice(1).replace('_', ' ')}</FormLabel>
<FormControl
data-testid={`edit_${detail}`}
type={detail}
value={details[detail] || ''}
onChange={handleDetailsChange}
/>
</FormGroup>
)
}
listOfForms.push(
<DatePicker
data-testid={'edit_dob'}
selected={dob}
onChange={setDob}
maxDate={new Date()}
/>
);
return(
listOfForms
)
}
Test
it('Displays the edit fields', () => {
cy.get('[data-testid="accountButton"]')
.click();
cy.get('[data-testid="editUserDetailsButton"]')
.click();
cy.get('[data-testid="user_email"]')
.should('not.be.visible');
cy.get('[data-testid="edit_full_name"]')
.should('be.visible');
cy.get('[data-testid="edit_dob"]')
.should('be.visible');
cy.get('[data-testid="edit_email"]')
.should('be.visible')
.should('have.value', 'test#editdetails.com');
})
Once I can target it, I plan to test clearing the current input and typing a new date in. Any help targeting the picker and warnings of further potential pitfalls are appreciated.
Edit: Here's the generated HTML of the form.
<form>
<div class="form-group">
<label class="form-label" for="email">Email</label>
<input data-testid="edit_email" type="email" id="email" class="form-control" value="admin#example.com" style="background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABHklEQVQ4EaVTO26DQBD1ohQWaS2lg9JybZ+AK7hNwx2oIoVf4UPQ0Lj1FdKktevIpel8AKNUkDcWMxpgSaIEaTVv3sx7uztiTdu2s/98DywOw3Dued4Who/M2aIx5lZV1aEsy0+qiwHELyi+Ytl0PQ69SxAxkWIA4RMRTdNsKE59juMcuZd6xIAFeZ6fGCdJ8kY4y7KAuTRNGd7jyEBXsdOPE3a0QGPsniOnnYMO67LgSQN9T41F2QGrQRRFCwyzoIF2qyBuKKbcOgPXdVeY9rMWgNsjf9ccYesJhk3f5dYT1HX9gR0LLQR30TnjkUEcx2uIuS4RnI+aj6sJR0AM8AaumPaM/rRehyWhXqbFAA9kh3/8/NvHxAYGAsZ/il8IalkCLBfNVAAAAABJRU5ErkJggg=="); background-repeat: no-repeat; background-attachment: scroll; background-size: 16px 18px; background-position: 98% 50%; cursor: auto;">
</div>
<div class="form-group">
<label class="form-label" for="full_name">Full name</label>
<input data-testid="edit_full_name" type="full_name" id="full_name" class="form-control" value="Jareth">
</div>
<div class="form-group">
<label class="form-label" for="address">Address</label>
<input data-testid="edit_address" type="address" id="address" class="form-control" value="">
</div>
<div class="form-group">
<label class="form-label" for="postcode">Postcode</label>
<input data-testid="edit_postcode" type="postcode" id="postcode" class="form-control" value="">
</div>
<div data-testid="edit_dob">
<div class="react-datepicker-wrapper">
<div class="react-datepicker__input-container">
<input type="text" class="" value="04/03/1999">
</div>
</div>
</div>
<button data-testid="confirmChangesButton" type="submit" class="LoaderButton btn btn-primary btn-block">Confirm Changes</button></form>

The problem is visible when looking at the HTML generated by running the app.
DatePicker HTML
<div data-testid="edit_dob">
<div class="react-datepicker-wrapper">
<div class="react-datepicker__input-container">
<input type="text" class="" value="04/03/1999">
</div>
</div>
</div>
The DatePicker is rendered as an input element inside a few divs, and the test id doesn't target the input.
Solution
The Selector Playground API for Cypress suggests cy.get('.react-datepicker__input-container > input'), the class of the parent div of the input. With this I can target the DatePicker, clear it, and type new input.
Notes For Next Time
Examine the HTML
Read the Docs more thoroughly

Reference the concepts used in the cy.pickDateRange command in the Cypress Real World App. It is a payment application to demonstrate real-world usage of Cypress testing methods, patterns, and workflows.
In the code for the command, you will find it is necessary to examine the HTML produced and traverse it appropriately for your needs.

Related

#material-ui/core - How to set Textarea Value

Im new to this plugin and could not find any documentation regarding my question.
I want to simply apply a value to the textarea. I started by giving it an id, but there are multiple html components that make up this textarea, ad more that ons instance of the id I added.
The value I enter gets saved, but when I reopen the modal, its suppose to load the text that was saved for that specific area.
<TextField
className="bd_textarea"
id={ `tt_description_text outlined-multiline-static`}
label="Pain description"
multiline
rows={5}
onBlur={ (f)=>(this.textOnBlur({id:answeringPainSpot.spotId, value:f.target.value, doc_name:answeringPainSpot.doc_name})) }
/>
Here are my attempts on both class and id and none worked.
$(".bd_textarea").val(spot_text);
$(".bd_textarea").next().find('textarea').eq(0).val(spot_text);
$('.bd_textarea').find('textarea').val(spot_text);
$("#tt_description_text").val(spot_text);
$("#tt_description_text").next().find('textarea').eq(0).val(spot_text);
$('#tt_description_text').find('textarea').val(spot_text);
This is the code when renderd.
<p class="MuiTypography-root MuiTypography-body1" id="modal-modal-description" sx="[object Object]">
<div class="MuiFormControl-root MuiTextField-root bd_textarea">
<label class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated" data-shrink="false" for="tt_description_text outlined-multiline-static" id="tt_description_text outlined-multiline-static-label">Pain description</label>
<div class="MuiInputBase-root MuiInput-root MuiInput-underline MuiInputBase-formControl MuiInput-formControl MuiInputBase-multiline MuiInput-multiline">
<textarea aria-invalid="false" id="tt_description_text outlined-multiline-static" rows="5" class="MuiInputBase-input MuiInput-input MuiInputBase-inputMultiline MuiInput-inputMultiline"></textarea>
</div>
</div>
</p>
Can you try setting the value like this:
<textarea
className="form-control"
rows="4"
placeholder="Add a comment"
value={this.state.newComment}
onChange={(e) => {
this.setState({
newComment: e.target.value,
);
}}>
</textarea>

React 'fetch' response successful, but setState not working

I'm new to React, working through a ".Net core CRUD with React" tutorial and trying to tweak it along the way to suit my own needs.
The page I'm dealing with here is an Add/Edit entry page. It works fine for rendering a default form with default values but doesn't render anything if the values are collected from a fetch call.
The important details are below:
interface AddPortfolioProjectDataState {
title: string;
projectData: PortfolioProject;
loading: boolean;
}
The page is told to render as follows:
public render() {
let contents = this.state.loading
? <p><em>Loading Project...</em></p>
: this.renderCreateForm(this.state.projectData.type, this.state.projectData.tech);
return (
<div>
<h1>{this.state.title}</h1>
<h3>Project</h3>
<hr />
{contents}
</div>
)
}
If I want to add a new item, therefore using a default PortfolioProject object with default values, it works fine. However, if I want to edit an old entry, I have to grab it from the server, like so:
fetch('api/StuartAitkenWebsite/GetPortfolioProject/' + projID)
.then(response => response.json() as Promise<PortfolioProject>)
.then(data => {
this.setState({ title: "Edit", loading: false, projectData: data });
});
In the debug console on Firefox, I can see the whole server process runs smoothly:
GET http://localhost:62669/api/StuartAitkenWebsite/GetPortfolioProject/2
Response payload: {"id":2,"name":"Particles Sim","projectDate":"2017-01-01T00:00:00","projectDurationWeeks":1,"type":"Desktop App","tech":"C++, SFML","views":0,"creationDate":"2018-10-22T00:00:00","modifiedDate":"2018-10-22T00:00:00","status":1}`
It gives a JSON output of the payload too, which I can't easily copy-paste here so I'll give a screenshot:
There are no server error responses, no React errors, nothing.
But that's as far as it gets.
The page remains showing 'loading', even though the data is there and ready and wants to be displayed.
From this, I can gather that the final step of the fetch call is not succeeding, because this.setState({ title: "Edit", loading: false, projectData: data }); is clearly not having any effect on the page data.
I have other fetch calls which look exactly the same but work fine. I can't see what I'm missing here.
The one and the only difference I notice is this:
When I use this component to create a fresh 'Add Project' form, the state is set like so:
this.state = {
title: "Create",
loading: false,
projectData: new PortfolioProject,
};
But when I do it from the API, it's set like so:
this.setState({
title: "Edit",
loading: false,
projectData: data
});
The successful version uses this.state, and the unsuccessful version uses this.setState
I don't know what this can mean though. As I said, no errors are being thrown, I'm sticking to the tutorial format, and it works fine in other parts of the project.
Thanks.
UPDATE
I've put a log in at the point where renderCreateForm() is called. It seems setState is actually working. Therefore, the problem must be in renderCreateForm() so I'll post that code below. Sorry it's sort of large.
private renderCreateForm(projectTypes: string, projectTech: string) {
console.log(this.state.loading); // "false"
console.log(this.state.projectData); //"Object { id:1, name: "Muon Detector".. etc
//so the render is getting the data
return (
<form onSubmit={this.handleSave}>
<div className="form-group row" >
<input type="hidden" name="Id" value={this.state.projectData.id} />
</div>
<div className="form-group row" >
<label className=" control-label col-md-12" htmlFor="Name">Name</label>
<div className="col-md-4">
<input className="form-control" type="text" name="Name" defaultValue={this.state.projectData.name} required />
</div>
</div>
<div className="form-group row" >
<label className=" control-label col-md-12" htmlFor="ProjectDate">Project Date</label>
<div className="col-md-4">
<input className="form-control" type="date" name="ProjectDate" defaultValue={this.state.projectData.creationDate.toDateString()} required />
</div>
</div >
<div className="form-group row" >
<label className=" control-label col-md-12" htmlFor="ProjectDurationWeeks">Project Duration (weeks)</label>
<div className="col-md-4">
<input className="form-control" type="text" name="ProjectDurationWeeks" defaultValue={this.state.projectData.projectDurationWeeks.toString()} required />
</div>
</div >
<div className="form-group row" >
<label className=" control-label col-md-12" htmlFor="Type">Project Type</label>
<div className="col-md-4">
<input className="form-control" type="text" name="Type" defaultValue={this.state.projectData.type} required />
</div>
</div >
<div className="form-group row" >
<label className=" control-label col-md-12" htmlFor="Tech">Project Tech</label>
<div className="col-md-4">
<input className="form-control" type="text" name="Tech" defaultValue={this.state.projectData.tech} required />
</div>
</div >
<div className="form-group row" >
<input type="hidden" name="Views" value={this.state.projectData.views} />
</div>
<div className="form-group row" >
<input type="hidden" name="CreationDate" value={this.state.projectData.creationDate.toDateString()} />
</div>
<div className="form-group row" >
<input type="hidden" name="ModifiedDate" value={this.state.projectData.modifiedDate.toDateString()} />
</div>
<div className="form-group row" >
<input type="hidden" name="Status" value={this.state.projectData.status} />
</div>
<div className="form-group">
<button type="submit" className="btn btn-default">Save</button>
<button className="btn" onClick={this.handleCancel}>Cancel</button>
</div >
</form>
)
}
UPDATE 2: Added some screenshots showing how things appear so far.
How the main data table page looks:
If I click 'Add New', it works:
(the 'Save' option works there too. Data posts to the server and will list on the main portfolio page)
Clicking Edit for any of the entries does not work, it gets this far:
The 'Loading Project...' text comes from the render() call for this page, as is shown in the code posted at the top of this post.
The page is supposed to look exactly like the 'Create' page (2nd screenshot), but with the title being 'Edit', and with input values populated from the given data.
The solution was absurd, but may certainly help others...
The renderCreateForm() method (as shown in Update 1 of the post) was not working because of the .toDateString() method I was using in a few of the inputs.
I changed it to .toString() and now everything works.
For example, with an input like this:
<input className="form-control" type="date" name="ProjectDate" defaultValue={projectData.creationDate.toDateString()} required />
I changed it to this:
<input className="form-control" type="date" name="ProjectDate" defaultValue={projectData.creationDate.toString()} required />
Note the defaultValue property of the input.
Repeat for all cases of .ToDateString(), and it now works,
Amazing that this didn't bring up an error anywhere. I thought Typescript and all these various frameworks were supposed to get around the issue of Javascript silently failing like that. This has been my longest and most time-wasting 'silent fail error' ever, by a very long margin.

Why aren't all my data validations not validating as designed?

I am developing this application in Angular 2 that has a form that you can populate and if the form is not populated, it will prompt you with what needs to be populated. It works for Customer Name, but then for the rest, for example, "a list of tools is required" does not go away once populated. Here is the code below under app/proposal/proposal-new.component.html:
<h1>Create a Proposal</h1>
<div>
<form #proposalForm="ngForm">
<div class="form-group">
<label for="customer">Customer Name</label>
<input type="text" id="customer" placeholder="ABC Supply" required name="customer" #customer='ngModel' [(ngModel)]="proposal.customer">
<div [hidden]="customer.valid || customer.pristine">
Customer name is required
</div>
</div>
<div class="form-group">
<label for="portfolio_url">Portfolio URL</label>
<input type="text" id="portfolio_url" placeholder="ABC Supply" required name="portfolio_url" #portfolio_url='ngModel' [(ngModel)]="proposal.portfolio_url">
<div [hidden]="portfolio_url.valid || portfolio_url.pristine">
A Portfolio URL is required
</div>
</div>
<div class="form-group">
<label for="portfolio_url">Tools that you'll use on the project</label>
<input type="text" id="portfolio_url" placeholder="Ruby on Rails, Angular, etc" required name="tools" #portfolio_url='ngModel' [(ngModel)]="proposal.tools ">
<div [hidden]="tools.valid || tools.pristine">
A list of tools is required
</div>
</div>
<div class="form-group">
<label for="estimated_hours">Estimated hours</label>
<input type="number" id="estimated_hours" required name="estimated_hours" #portfolio_url='ngModel' [(ngModel)]="proposal.estimated_hours ">
<div [hidden]="estimated_hours.valid || estimated_hours.pristine">
You need to enter your estimated hours for the project
</div>
</div>
<div class="form-group">
<label for="hourly_rate">Hourly rate</label>
<input type="number" id="hourly_rate" required name="hourly_rate" #portfolio_url='ngModel' [(ngModel)]="proposal.hourly_rate ">
<div [hidden]="hourly_rate.valid || hourly_rate.pristine">
You need to enter your hourly rate for the project
</div>
</div>
<div class="form-group">
<label for="weeks_to_complete">Weeks to Complete</label>
<input type="number" id="weeks_to_complete" required name="weeks_to_complete" #weeks_to_complete='ngModel' [(ngModel)]="proposal.weeks_to_complete ">
<div [hidden]="weeks_to_complete.valid || weeks_to_complete.pristine">
You need to enter the weeks you estimate to complete the project
</div>
</div>
<div class="form-group">
<label for="weeks_to_complete">Client Email <em>(Optional)</em></label>
<input type="email" id="weeks_to_complete" name="weeks_to_complete" #client_email='ngModel' [(ngModel)]="proposal.client_email">
</div>
</form>
<div>
<p>Hi {{proposal.customer}},</p>
<p>It was a pleasure getting to meet with you and review your project requirements, I believe it is a great fit for the types of applications that I build out. Please feel free to check out some of my past projects here.</p>
<p>To successfully build out the application I will be utilizing {{proposal.tools}}, and a number of other tools to ensure that the project follows industry wide best practices.</p>
<p>Ensuring that you are fully informed is one of my top priorities, so in addition to incorporating everything we discussed, I will also be creating a project management dashboard and sending you a project update message daily so that you can have a transparent view of the development as it takes place.</p>
<p>To accomplish the project and meet the requirements that we discussed, I am estimating that it will take {{proposal.estimated_hours}} hours in development time to complete. The project should take {{proposal.weeks_to_complete}} weeks to complete and with my hourly rate of {{proposal.hourly_rate}}/hour, the estimated total will be {{proposal.hourly_rate * proposal.estimated_hours}}.</p>
<p>The next step from here is to set up a meeting to finalize the project and sign contracts.</p>
<p>I am excited to working with you and build out this project.</p>
</div>
Here is the app/proposal/proposal.ts:
export class Proposal {
constructor(
public id?: number,
public customer?: string,
public portfolio_url: string = 'http://',
public tools?: string,
public estimated_hours?: number,
public hourly_rate?: number,
public weeks_to_complete?: number,
public client_email?: string,
) {}
}
If there is any other files I should be looking at and posting, please let me know. I am stumped here.
The issue you are seeing is that the local variable portfolio_url is being defined multiple times instead of a new local variable for each input. If you update the local variable definitions, which look like this: #[var_name]='ngModel', to have a unique name for each the validation should work as expected.
Update your HTML to the following:
<h1>Create a Proposal</h1>
<div>
<form #proposalForm="ngForm">
<div class="form-group">
<label for="customer">Customer Name</label>
<input type="text"
id="customer"
placeholder="ABC Supply"
required name="customer"
#customer='ngModel'
[(ngModel)]="proposal.customer">
<div [hidden]="customer.valid || customer.pristine">
Customer name is required
</div>
</div>
<div class="form-group">
<label for="portfolio_url">Portfolio URL</label>
<input type="text"
id="portfolio_url"
placeholder="ABC Supply"
required
name="portfolio_url"
#portfolio_url='ngModel'
[(ngModel)]="proposal.portfolio_url">
<div [hidden]="portfolio_url.valid || portfolio_url.pristine">
A Portfolio URL is required
</div>
</div>
<div class="form-group">
<label for="portfolio_url">Tools that you'll use on the project</label>
<input type="text"
id="tools"
placeholder="Ruby on Rails, Angular, etc"
required
name="tools"
#tools='ngModel'
[(ngModel)]="proposal.tools ">
<div [hidden]="tools.valid || tools.pristine">
A list of tools is required
</div>
</div>
<div class="form-group">
<label for="estimated_hours">Estimated hours</label>
<input type="number"
id="estimated_hours"
required
name="estimated_hours"
#estimated_hours='ngModel'
[(ngModel)]="proposal.estimated_hours ">
<div [hidden]="estimated_hours.valid || estimated_hours.pristine">
You need to enter your estimated hours for the project
</div>
</div>
<div class="form-group">
<label for="hourly_rate">Hourly rate</label>
<input type="number"
id="hourly_rate"
required
name="hourly_rate"
#hourly_rate='ngModel'
[(ngModel)]="proposal.hourly_rate ">
<div [hidden]="hourly_rate.valid || hourly_rate.pristine">
You need to enter your hourly rate for the project
</div>
</div>
<div class="form-group">
<label for="weeks_to_complete">Weeks to Complete</label>
<input type="number"
id="weeks_to_complete"
required
name="weeks_to_complete"
#weeks_to_complete='ngModel'
[(ngModel)]="proposal.weeks_to_complete ">
<div [hidden]="weeks_to_complete.valid || weeks_to_complete.pristine">
You need to enter the weeks you estimate to complete the project
</div>
</div>
<div class="form-group">
<label for="weeks_to_complete">Client Email <em>(Optional)</em></label>
<input type="email"
id="weeks_to_complete"
name="weeks_to_complete"
#client_email='ngModel'
[(ngModel)]="proposal.client_email">
</div>
</form>
<div>
<p>Hi {{proposal.customer}},</p>
<p>It was a pleasure getting to meet with you and review your project requirements, I believe it is a great fit for the types of applications that I build out. Please feel free to check out some of my past projects here.</p>
<p>To successfully build out the application I will be utilizing {{proposal.tools}}, and a number of other tools to ensure that the project follows industry wide best practices.</p>
<p>Ensuring that you are fully informed is one of my top priorities, so in addition to incorporating everything we discussed, I will also be creating a project management dashboard and sending you a project update message daily so that you can have a transparent view of the development as it takes place.</p>
<p>To accomplish the project and meet the requirements that we discussed, I am estimating that it will take {{proposal.estimated_hours}} hours in development time to complete. The project should take {{proposal.weeks_to_complete}} weeks to complete and with my hourly rate of {{proposal.hourly_rate}}/hour, the estimated total will be {{proposal.hourly_rate * proposal.estimated_hours}}.</p>
<p>The next step from here is to set up a meeting to finalize the project and sign contracts.</p>
<p>I am excited to working with you and build out this project.</p>
</div>
Working Plunkr
You shouldn't need to do this:
#portfolio_url='ngModel' [(ngModel)]="proposal.portfolio_url"
In the Angular 2 docs they say:
The hash (#) prefix to "phone" means that we're defining a phone variable
So in your case you are defining portfolio_url multiple times in your code. You do not need to use the hash prefix because the portfolio_url is already defined in your proposal object.
The following should work (read more about the template binding syntax):
<input type="number" id="hourly_rate" required name="hourly_rate" [(value)]="proposal.hourly_rate">
Or you can do this:
<input type="number" id="hourly_rate" required name="hourly_rate" #proposal.hourly_rate>
The angular 2 docs also have another example of user input: https://angular.io/docs/ts/latest/guide/user-input.html

Why is click ignored on non-Angular page in a Protractor test?

I have an app I'm trying to do e2e tests with. The app uses a non-Angular login page (Microsoft AD account). So, I fill in the user and password then click the "sign in" button with the following code:
var user = asAdmin ? config.adminUser : config.user;
browser.driver.findElement(by.id('cred_userid_inputtext')).sendKeys(user);
browser.driver.findElement(by.id('cred_password_inputtext')).sendKeys(config.pass);
browser.driver.findElement(by.id('cred_sign_in_button')).click();
The user and password get filled in and the "sign in" button (which is a <span>) looks like it was clicked (changes color), but nothing happens.
I wait for quite a while (minutes) and it just doesn't work. However, if I just use the mouse, it does work. I have also tried tacking on a "\n" to the end of the password string to simulate the enter key. This is ignored.
The html for the pertinent parts of the login look like this:
<form id="credentials" method="post" action="https://login.microsoftonline.com/...">
<div id="cred_userid_container" class="login_textfield textfield">
<span class="input_field textfield">
<label for="cred_userid_inputtext" class="no_display" aria-hidden="true">User account</label>
<div class="input_border">
<input tabindex="1" id="cred_userid_inputtext" class="login_textfield textfield required email field normaltext" placeholder="someone#example.com " type="email" name="login" spellcheck="false" alt="someone#example.com " aria-label="User account" value="" autocomplete="off">
</div>
</span>
</div>
...
<div id="cred_password_container" class="login_textfield textfield" style="opacity: 1;">
<span class="input_field textfield">
<label for="cred_password_inputtext" class="no_display" aria-hidden="true">Password</label>
<div class="input_border">
<input tabindex="2" id="cred_password_inputtext" class="login_textfield textfield required field normaltext" placeholder="Password" spellcheck="false" aria-label="Password" alt="Password" type="password" name="passwd" value="">
</div>
</span>
</div>
...
<li class="login_cred_options_container">
<div id="cred_kmsi_container" class="subtext normaltext">
<span class="input_field ">
<input tabindex="10" id="cred_keep_me_signed_in_checkbox" type="checkbox" value="0" name="persist">
<label id="keep_me_signed_in_label_text" for="cred_keep_me_signed_in_checkbox" class="persist_text">Keep me signed in</label>
</span>
</div>
<span id="cred_sign_in_button" tabindex="11" onclick="Post.SubmitCreds();return false;" class="button normaltext cred_sign_in_button refresh_domain_state disabled_button" role="button" style="opacity: 1;">Sign in</span>
<div id="recover_container" class="subtext smalltext" style="opacity: 1;">
<span>
<a id="cred_forgot_password_link" tabindex="12" href="https://login.microsoftonline.com/resetpw.srf?lc=1033&id=501148">Can’t access your account?</a>
</span>
</div>
</li>
...
</form>
I can't, for the life of me figure out what's going on here.
Any help is greatly appreciated.
UPDATED: added the missing "button" span
UPDATE 2: also tested with Firefox, same problem. The click on the span just doesn't seem to work..
UPDATE 3: For some strange reason, the following code works:
browser.actions()
.mouseMove(browser.driver.findElement(by.id('cred_sign_in_button')))
.click()
.perform();
browser.sleep(500);
browser.driver.findElement(by.id('cred_sign_in_button')).click();
If I take either the mouseMove/click or the last click out, it stops working. If I take out the sleep, it is intermittent.
I have no idea why that's working.
Testing non-angular pages with Protractor can be tricky regarding waiting for stuff.
I suggest you upgrade Protractor to latest (1.3.1 as of now), use a custom function waitReady() that browser.wait for elements ready and rewrite your test like this:
var user = asAdmin ? config.adminUser : config.user;
// TODO: use page objects
var userIdInputElm = $('#cred_userid_inputtext');
var passwordInputElm = $('#cred_password_inputtext');
var signinButtonElm = $('#cred_sign_in_button');
it('waits for the elements present and visible (non-angular)', function() {
expect(userIdInputElm.waitReady()).toBeTruthy();
expect(passwordInputElm.waitReady()).toBeTruthy();
expect(signinButtonElm.waitReady()).toBeTruthy();
});
it('fills user and password', function() {
userIdInputElm.sendKeys(user);
passwordInputElm.sendKeys(config.pass);
});
it('clicks sign in button', function() {
signinButtonElm.click();
});
More details of why waitReady here.

Validating nested form in angular

Having this ordinary (name attribute is requred by server) form with angular and can't figured out how to make validations work. What should i put into ng-show="TODO"
http://jsfiddle.net/Xk3VB/7/
<div ng-app>
<form ng-init="variants = [{duration:10, price:100}, {duration:30, price:200}]">
<div ng-repeat="variant in variants" ng-form="variant_form">
<div>
<label>Duration:</label>
<input name="variants[{{$index}}][duration]" ng-model="variant.duration" required />
<span ng-show="TODO">Duration required</span>
</div>
<div>
<label>Price:</label>
<input name="variants[{{$index}}][price]" ng-model="variant.price" />
<span ng-show="TODO">Price required</span>
</div>
</div>
</form>
</div>
ps: this is just piece of form, which is more complicated
Thanks
AngularJS relies on input names to expose validation errors.
Unfortunately, as of today it is not possible (without using a custom directive) to dynamically generate a name of an input. Indeed, checking input docs we can see that the name attribute accepts a string only.
Long story short you should rely on ng-form to validate dynamically created inputs. Something like :
<div ng-repeat="variant in variants" >
<ng-form name="innerForm">
<div>
<label>Duration:</label>
<input name="duration" ng-model="variant.duration" required />
<span ng-show="innerForm.duration.$error.required">Duration required</span>
</div>
<div>
<label>Price:</label>
<input name="price" ng-model="variant.price" required/>
<span ng-show="innerForm.price.$error.required">Price required</span>
</div>
</ng-form>
Working fiddle here
UPDATE : Base on your serverside requirement why not do something like that :
<input type="hidden" name="variants[{{$index}}][duration]" ng-model="variant.duration"/>
<input name="duration" ng-model="variant.duration" required />
The hidden input will be the one read by the server while the other one will be used to do the client side validation (later discarded by server). It s kind of an hack but should work.
PS : Be sure that your form is valid before actually submitting it. Can be done with ng-submit

Resources