I am currently working with Cypress and have created a function that randomly clicks an element from a list. I am trying to also get the text from that same function for future assertions. The problem is I am unable to return the text properly. The code I currently have is:
export function selectRandomFromList(listLocator, elementLocator) {
cy.get(listLocator).within(() => {
let numberOfElements = Cypress.$(listLocator + ' ' + elementLocator).length
let selected = Cypress._.random(0, numberOfElements - 1)
cy.get(elementLocator).eq(selected).then(($text) => {
const text = $text.text()
return text
}).click()
})
}
I was hoping that I could then in my test run this function, do the click and then store the returned text in a variable for future checking. What am I doing wrong? Also tried some other stuff with promises and such, when the code was saying I am trying to mix sync and async..
Forgot to add. This is in the support file and I want to use the text variable in the test file. Something like this:
var text = function.selectRandomFromList('[class*=***]', 'li ul li button')
After which I should have the text of the clicked button in text.
Cypress commands work the same way as Promises. You need to return a Promise when inside a then() callback. See doc here.
You may use cy.wrap($text).invoke('text') inside your then() callback instead of returning a string. But then, your click() would not work because your then() would yield a text value that cannot be clicked.
I suggest not using the within() and working directly with the elements. You'll end up with the same result, but with less complexity. I use the within() command when I need to perform several actions on several elements within a div container for example.
You can perform what you need with default commands instead of a function:
let mixLocator = listLocator + ' ' + elementLocator;
cy.get(mixLocator).its('length').then(elementCount => {
let selected = Cypress._.random(elementCount - 1); // lower = 0 is default
cy.get(mixLocator).eq(selected).click().invoke('text').as('selectedText'); // saving the text as an alias to be used later
});
Here, the invoke('text') should still work even if it is after the click(), but it is impossible to do the opposite invoke('text').click() because the invoke('text') command yields a string. If it doesn't, call it once to get the text and again to click it:
cy.get(mixLocator).eq(selected).invoke('text').as('selectedText');
cy.get(mixLocator).eq(selected).click();
or:
cy.get(mixLocator).eq(selected).then(selectedElement => {
cy.wrap(selectedElement).invoke('text').as('selectedText');
cy.wrap(selectedElement).click();
});
You may then use your saved alias later:
cy.get('#selectedText').then(selectedText => {
// use selectedText here
});
I often prefer to chose random elements in our tests to have a better coverage. I came up with a custom commands:
commands.js
Cypress.Commands.add('any', { prevSubject: 'element' }, (subject, size = 1) => {
cy.wrap(subject).then(elementList => {
elementList = (elementList.jquery) ? elementList.get() : elementList;
elementList = Cypress._.sampleSize(elementList, size);
elementList = (elementList.length > 1) ? elementList : elementList[0];
cy.wrap(elementList);
});
});
I use it like this:
cy.get(elementLocator).any().click();
or
cy.get(elementLocator).any(5).each(randomElement => {
cy.wrap(randomElement).click();
});
Related
i'm working on a simple note-taking app for my portfolio using JS and Firebase. Before i tell you what's happening i feel like i need to show you how my code works, if you have any tips and concerns please tell me as it would be GREATLY appreciated. That being said, let's have a look "together". I'm using this class to create the notes:
const htmlElements = [document.querySelector('.notes'), document.querySelector('.note')];
const [notesDiv, noteDiv] = htmlElements;
class CreateNote {
constructor(title, body) {
this.title = title;
this.body = body;
this.render = () => {
const div1 = document.createElement('div');
div1.className = 'notes-prev-container';
div1.addEventListener('click', () => { this.clickHandler(this) });
const div2 = document.createElement('div');
div2.className = 'notes-prev';
const hr = document.createElement('hr');
hr.className = 'notes__line';
// Nest 'div2' inside 'div1'
div1.appendChild(div2);
div1.appendChild(hr);
/*
Create Paragraph 1 & 2 and give them the same
class name and some text
*/
const p1 = document.createElement('p');
p1.className = 'notes-prev__title';
p1.innerText = this.title;
const p2 = document.createElement('p');
p2.className = 'notes-prev__body';
p2.innerText = this.body;
// Nest p 1 & 2 inside 'div2'
div2.appendChild(p1);
div2.appendChild(p2);
// Finally, render the div to its root tag
notesDiv.appendChild(div1);
}
}
/*
Every time this method is called, it creates 2 textareas,
one for the note title and the other for its body then it
appends it to the DOM.
*/
renderNoteContent () {
const title = document.createElement('textarea');
title.placeholder = 'Title';
title.value = this.title;
title.className = 'note__title';
const body = document.createElement('textarea');
body.placeholder = 'Body';
body.value = this.body;
body.className = 'note__body';
noteDiv.appendChild(title);
noteDiv.appendChild(body);
}
/*
When this method is called, it checks to see if there's a
note rendered already (childElementCount === 1 because there's a
button, so if there's only this button it means there's no
textareas rendered).
If yes, then merely call the renderNoteContent method. Else
get the tags with the classes 'note__title' and 'note__body'
and remove them from the DOM, then call renderNoteContent to
create the textareas with the clicked notes values.
This function gets mentioned at line 19.
*/
clickHandler(thisClass) {
if (noteDiv.childElementCount === 1) {
thisClass.renderNoteContent();
} else {
document.querySelector('.note__title').remove();
document.querySelector('.note__body').remove();
thisClass.renderNoteContent();
}
}
}
Now i need 2 buttons, createNotesButton and saveNotesButton respectively. These 2 buttons must be inside a function that will be called inside .onAuthStateChanged (why? because they will be needing access to the currentUser on firebase auth).
I want the createNotesButton to create a note prototype, render it to the DOM and create a new document on firestore, where this note contents will be stored. Here's how i did it:
PS: I feel like i'm not using this class correctly, so again if you have any tips i appreciate it.
import {db} from '../../firebase_variables/firebase-variables.js';
import {CreateNote} from '../create_notes_class/create_notes_class.js';
const htmlElements = [
document.querySelector('.createNotes-button'),
document.querySelector('.saveNotes-button')
];
const [createNotesButton, saveNotesButton] = htmlElements;
function clickHandler(user) {
/*
1. Creates a class.
2. Creates a new document on firebase with the class's empty value.
3. Renders the empty class to the DOM.
*/
createNotesButton.addEventListener('click', () => {
const note = new CreateNote('', '');
note.render();
// Each user has it's own note collection, said collection has their `uid` as name.
db.collection(`${user.uid}`).doc().set({
title: `${note.title}`,
body: `${note.body}`
})
})
}
Now i need a saveNotesButton, he's the one i'm having issues with. He needs to save the displayed note's content on firestore. Here's what i tried doing:
import {db} from '../../firebase_variables/firebase-variables.js';
import {CreateNote} from '../create_notes_class/create_notes_class.js';
const htmlElements = [
document.querySelector('.createNotes-button'),
document.querySelector('.saveNotes-button')
];
const [createNotesButton, saveNotesButton] = htmlElements;
function clickHandler(user) {
createNotesButton.addEventListener('click', () => {...})
/*
1. Creates 2 variables, `title` and `body, if there's not a note being displayed
their values will be null, which is why the rest of the code is inside an if
statement
2. If statement to check if there's a note being displayed, if yes then:
1. Call the user's note collection. Any document who has the title field equal to the
displayed note's value gets returned as a promise.
2. Then call an specific user document and update the fields `title` and `body` with
the displayed note's values.
3. If no then do nothing.
*/
saveNotesButton.addEventListener('click', () => {
const title = document.querySelector('.note__title');
const body = document.querySelector('.note__body');
db.collection(`${user.uid}`).where('title', '==', `${title.value}`)
.get()
.then(userCollection => {
db.collection(`${user.uid}`).doc(`${userCollection.docs[0].id}`).update({
title: `${title.value}`,
body: `${body.value}`
})
})
.catch(error => {
console.log('Error getting documents: ', error);
});
});
}
This didn't work because i'm using title.value as a query, so if i change it's value it will also change the queries direction to a path that doesn't exist.
So here's the question: how can i make it so the saveNotesButton does its job? I was thinking of adding another field to each note, something that won't change so i can easily identify and edit each note. Again, if there's something in my code that you think can or should be formatted please let me know, i'm using this project as a way to solidify my native JS knowledge so please be patient. I feel like if i had used React i would've finished this sometime ago but definitely wouldn't have learned as much, anyway thanks for your help in advance.
I was thinking of adding another field to each note, something that won't change so i can easily identify and edit each note.
Yes, you absolutely need an immutable identifier for each note document in the firestore so you can unambiguously reference it. You almost always want this whenever you're storing a data object, in any application with any database.
But, the firestore already does this for you: after calling db.collection(user.uid).doc() you should get a doc with an ID. That's the ID you want to use when updating the note.
The part of your code that interacts with the DOM will need to keep track of this. I suggest moving the code the creates the firestore document into the constructor of CreateNote and storing it on this. You'll need the user id there as well.
constructor(title, body, userId) {
this.title = title;
this.body = body;
const docRef = db.collection(userId).doc();
this.docId = docRef.id;
/* etc. */
Then any time you have an instance of CreateNote, you'll know the right user and document to reference.
Other suggestions (since you asked)
Use JsPrettier. It's worth the setup, you'll never go back.
Use HTML semantics correctly. Divs shouldn't be appended as children of hrs, because they're for "a thematic break between paragraph-level elements: for example, a change of scene in a story, or a shift of topic within a section." MDN
For your next project, use a framework. Essentially no one hand-codes event listeners and appends children to get things done. I see the value for basic understanding, but there's a rich and beautiful world of frameworks out there; don't limit yourself by avoiding them :-)
I am learning Javascript. I am working on reading RSS feeds for a personal project. I am using 'RSS-parser' npm library to avoid CORS error.
And also I am using Browserify bundler to make it work on the browser.
When I run this code on the terminal it gives me output without any issue. But when I try with the browser it prints nothing.
My knowledge about Asynchronous JS is limited but I am pretty sure it doesn't have errors in here as I added code to it without changing existing code.
let Parser = require('rss-parser');
let parser = new Parser();
let feed;
async () => {
feed = await parser.parseURL('https://www.reddit.com/.rss');
feedTheList();
};
// setTimeout(function() {
// //your code to be executed after 1 second
// feedTheList();
// }, 5000);
function feedTheList()
{
document.body.innerHTML = "<h1>Total Feeds: " + feed.items.length + "</h1>";
let u_list = document.getElementById("list")[0];
feed.items.forEach(item => {
var listItem = document.createElement("li");
//Add the item text
var newText = document.createTextNode(item.title);
listItem.appendChild(newText);
listItem.innerHTML =item.title;
//Add listItem to the listElement
u_list.appendChild(listItem);
});
}
Here is my HTML code.
<body>
<ul id="list"></ul>
<script src="bundle.js"></script>
</body>
Any guidance is much appreciated.
document.getElementById() returns a single element, not a collection, so you don't need to index it. So this:
let u_list = document.getElementById("list")[0];
sets u_list to `undefined, and you should be getting errors later in the code. It should just be:
let u_list = document.getElementById("list");
Also, when you do:
listItem.innerHTML =item.title;
it will replace the text node that you appended on the previous line with this HTML. Either append the text node or assign to innerHTML (or more correctly, innerText), you don't need to do both.
Looks like the async call is not being executed; You need to wrap it
in an anonymous function call:
See the example here:
https://www.npmjs.com/package/rss-parser
Essentially,
var feed; // change let to var, so feed can be used inside the function
// wrap the below into a function call
(async () => {
feed = await parser.parseURL('https://www.reddit.com/.rss');
feedTheList();
})(); // the (); at the end executes the promise
Now it will execute and feed should have items.
CORS errors when making request
As noted in the documentation at https://www.npmjs.com/package/rss-parser, if you get CORS error on a resource, use a CORS proxy. I've updated their example to fit your code:
// Note: some RSS feeds can't be loaded in the browser due to CORS security.
// To get around this, you can use a proxy.
const CORS_PROXY = "https://cors-anywhere.herokuapp.com/"
let parser = new RSSParser();
(async () => {
await parser.parseURL(CORS_PROXY + 'https://www.reddit.com/.rss', function(err, feed) {
feedTheList(feed);
});
})();
function feedTheList(feed)
{
// unchanged
}
One last thing:
The line
document.body.innerHTML = "<h1>Total Feeds: " + feed.items.length + "</h1>";
Will remove all of the content of <body>
I suggest to look into how element.appendChild works, or just place the <h1> tag in your HTML and modify its innerHTML property instead.
I am trying to aggregate a list of dates from a data table, written in Angular, in a Protractor test. I'm doing the aggregation from a PageObject class that is called in the Protractor test. I know that my code is successfully grabbing the text I want, but when I try to console.log the returned array, I get an empty array. I'm still new to Javascript/Typescript, Angular, and Protractor and this may be a result of my newness to the asynchronous nature of this development environment.
Code is as follows,
The PageObject SpecMapper class with method:
import { browser, element, by } from 'protractor';
export class SpecMapperPage {
getImportDateSubmittedColumnValues() {
let stringDatesArray: Array<string> = [];
// currently this css selector gets rows in both import and export tables
// TODO: get better identifiers on the import and export tables and columns
element.all(by.css('md-card-content tbody tr.ng-tns-c3-0')).each(function(row, index){
// check outerHTML for presence of "unclickable", the rows in the export table
row.getAttribute('outerHTML').then(function(outerHTML:string) {
// specifically look for rows without unclickable
if(outerHTML.indexOf("unclickable") < 0){
// grab the columns and get the third column, where the date submitted field is
// TODO: get better identifiers on the import and export columns
row.all(by.css("td.ng-tns-c3-0")).get(2).getText().then(function(text:string) {
stringDatesArray.push(text);
});
}
});
});
return stringDatesArray;
}
}
I know it's not the prettiest code, but it's temporary place holder while my devs make me better attributes/classes/ids to grab my variables. Key things to note is that I create a string Array to hold the values I consider relevant to be returned when the method is finished.
I used WebStorm and put a breakpoint at the stringDatesArray.push(text) and return stringDatesArray lines. The first line shows that the text variable has a string variable that I'm looking for and is successfully getting pushed. I see the success in debug mode as I can see the stringDatesArray and see the values in it. The second line though, the array return, shows that the local variable stringDatesArray is empty. This is echoed in the following code when I try to console.log the array:
The Protractor run Spec class with my test in it:
import { SpecMapperPage } from "./app.po";
import {browser, ExpectedConditions} from "protractor";
describe('spec mapper app', () => {
let page: SpecMapperPage;
let PROJECT_ID: string = '57';
let PROJECT_NAME: string = 'DO NOT DELETE - AUTOMATED TESTING PROJECT';
beforeEach(() => {
page = new SpecMapperPage();
});
describe('import/export page', () => {
it('verify sort order is desc', () => {
browser.waitForAngularEnabled(false);
// Step 1: Launch Map Data from Dashboard
page.navigateTo(PROJECT_ID);
browser.driver.sleep(5000).then(() => {
// Verify: Mapping Screen displays
// Verify on the specmapper page by checking the breadcrumbs
expect(page.getProjectNameBreadCrumbText()).toContain(PROJECT_NAME);
expect(page.getProjectMapperBreadCrumbText()).toEqual("MAPPER");
// Verify: Verify Latest Submitted Date is displayed at the top
// Verify: Verify the Submitted Date column is in descending order
console.log(page.getImportDateSubmittedColumnValues());
});
});
});
});
I acknowledge that this code is not actively using the niceties of Protractor, there's a known issue with our app that will not be addressed for a couple of months, so I am accessing the driver directly 99% of the time.
You'll note that I call the method I posted above as the very last line in the browser.driver.sleep().then() clause, page.getImportDateSubmittedColumnValues().
I thought maybe I was running into asynchronous issues with the call being done before the page was loaded, thus I put it in the .then() clause; but learned with debugging that was not the case. This code should work once I have the array returning properly though.
The console.log is printing an empty [] array. That is synonymous with the results I saw when debugging the above method directly in the PageObject SpecMapper class. I wish to do some verification that the strings are returned properly formatted, and then I'm going to do some date order comparisons. I feel like returning an array of data retrieved from a page is not an unusual request, but I can't seem to find a good way to Google what I'm trying to do.
My apologies if I am hitting some very obvious roadblock, I'm still learning the nuances of Typescript/Angular/Protractor. Thank you for your consideration!
My attempted to used collated promises seemed promising, but fell through on execution.
My Updated PageObject SpecMapper Class
import {browser, element, by, protractor} from 'protractor';
export class SpecMapperPage {
getImportDateSubmittedColumnValues() {
let promisesArray = [];
let stringDatesArray: Array<string> = [];
// This CSS selector grabs the import table and any cells with the label .created-date
element.all(by.css('.import-component .created-date')).each(function(cell, index) {
// cell.getText().then(function(text:string) {
// console.log(text);
// });
promisesArray.push(cell.getText());
});
return protractor.promise.all(promisesArray).then(function(results) {
for(let result of results) {
stringDatesArray.push(result);
}
return stringDatesArray;
});
}
}
My Updated Spec test Using The Updated SpecMapper PO Class
import { SpecMapperPage } from "./specMapper.po";
import {browser, ExpectedConditions} from "protractor";
describe('spec mapper app', () => {
let page: SpecMapperPage;
let PROJECT_ID: string = '57';
let PROJECT_NAME: string = 'DO NOT DELETE - AUTOMATED TESTING PROJECT';
beforeEach(() => {
page = new SpecMapperPage();
});
describe('import/export page', () => {
it('TC2963: ImportComponentGrid_ShouldDefaultSortBySubmittedDateInDescendingOrder_WhenPageIsLoaded', () => {
browser.waitForAngularEnabled(false);
// Step 1: Launch Map Data from Dashboard
page.navigateTo(PROJECT_ID);
browser.driver.sleep(5000).then(() => {
// Verify: Mapping Screen displays
// Verify on the specmapper page by checking the breadcrumbs
expect(page.getProjectNameBreadCrumbText()).toContain(PROJECT_NAME);
expect(page.getProjectMapperBreadCrumbText()).toEqual("MAPPER");
// Verify: Verify Latest Submitted Date is displayed at the top
// Verify: Verify the Submitted Date column is in descending order
page.getImportDateSubmittedColumnValues().then(function(results) {
for(let value of results) {
console.log("a value is: " + value);
}
});
});
});
});
});
When I breakpoint in the PO class at the return stringDatesArray; line, I have the following variables in my differing scopes. Note that the promisesArray has 3 objects, but the results array going into the protractor.promise.all( block has 0 objects. I'm not sure what my disconnect is. :/
I think I'm running into a scopes problem that I am having issues understanding. You'll note the commented out promise resolution on the getText(), and this was my POC proving that I am getting the string values I'm expecting, so I'm not sure why it's not working in the Promise Array structure presented as a solution below.
Only other related question that I could find has to do with grabbing a particular row of a table, not specifically aggregating the data to be returned for test verification in Protractor. You can find it here if you're interested.
As you've alluded to your issue is caused by the console.log returning the value of the variable before its actually been populated.
I've taken a snippet from this answer which should allow you to solve it: Is there a way to resolve multiple promises with Protractor?
var x = element(by.id('x')).sendKeys('xxx');
var y = element(by.id('y')).sendKeys('yyy');
var z = element(by.id('z')).sendKeys('zzz');
myFun(x,y,z);
//isEnabled() is contained in the expect() function, so it'll wait for
// myFun() promise to be fulfilled
expect(element(by.id('myButton')).isEnabled()).toBe(true);
// in a common function library
function myFun(Xel,Yel,Zel) {
return protractor.promise.all([Xel,Yel,Zel]).then(function(results){
var xText = results[0];
var yText = results[1];
var zText = results[2];
});
}
So in your code it would be something like
getImportDateSubmittedColumnValues() {
let promisesArray = [];
let stringDatesArray: Array<string> = [];
// currently this css selector gets rows in both import and export tables
// TODO: get better identifiers on the import and export tables and columns
element.all(by.css('md-card-content tbody tr.ng-tns-c3-0')).each(function(row, index){
// check outerHTML for presence of "unclickable", the rows in the export table
row.getAttribute('outerHTML').then(function(outerHTML:string) {
// specifically look for rows without unclickable
if(outerHTML.indexOf("unclickable") < 0){
// grab the columns and get the third column, where the date submitted field is
// TODO: get better identifiers on the import and export columns
promisesArray.push(row.all(by.css("td.ng-tns-c3-0")).get(2).getText());
}
});
});
return protractor.promise.all(promisesArray).then(function(results){
// In here you'll have access to the results
});
}
Theres quite a few different ways you could do it. You could process the data in that method at the end or I think you could return the array within that "then", and access it like so:
page.getImportDateSubmittedColumnValues().then((res) =>{
//And then here you will have access to the array
})
I don't do the Typescript but if you're just looking to get an array of locator texts back from your method, something resembling this should work...
getImportDateSubmittedColumnValues() {
let stringDatesArray: Array<string> = [];
$$('.import-component .created-date').each((cell, index) => {
cell.getText().then(text => {
stringDatesArray.push(text);
});
}).then(() => {
return stringDatesArray;
});
}
The answer ended up related to the answer posted on How do I return the response from an asynchronous call?
The final PageObject class function:
import {browser, element, by, protractor} from 'protractor';
export class SpecMapperPage {
getImportDateSubmittedColumnValues() {
let stringDatesArray: Array<string> = [];
let promisesArray = [];
// return a promise promising that stringDatesArray will have an array of dates
return new Promise((resolve, reject) => {
// This CSS selector grabs the import table and any cells with the label .created-date
element.all(by.css('.import-component .created-date')).map((cell) => {
// Gather all the getText's we want the text from
promisesArray.push(cell.getText());
}).then(() => {
protractor.promise.all(promisesArray).then((results) => {
// Resolve the getText's values and shove into array we want to return
for(let result of results) {
stringDatesArray.push(result);
}
}).then(() => {
// Set the filled array as the resolution to the returned promise
resolve(stringDatesArray);
});
});
});
}
}
The final test class:
import { SpecMapperPage } from "./specMapper.po";
import {browser, ExpectedConditions} from "protractor";
describe('spec mapper app', () => {
let page: SpecMapperPage;
let PROJECT_ID: string = '57';
let PROJECT_NAME: string = 'DO NOT DELETE - AUTOMATED TESTING PROJECT';
beforeEach(() => {
page = new SpecMapperPage();
});
describe('import/export page', () => {
it('TC2963: ImportComponentGrid_ShouldDefaultSortBySubmittedDateInDescendingOrder_WhenPageIsLoaded', () => {
browser.waitForAngularEnabled(false);
// Step 1: Launch Map Data from Dashboard
page.navigateTo(PROJECT_ID);
browser.driver.sleep(5000).then(() => {
// Verify: Mapping Screen displays
// Verify on the specmapper page by checking the breadcrumbs
expect(page.getProjectNameBreadCrumbText()).toContain(PROJECT_NAME);
expect(page.getProjectMapperBreadCrumbText()).toEqual("MAPPER");
// Verify: Verify Latest Submitted Date is displayed at the top
// Verify: Verify the Submitted Date column is in descending order
page.getImportDateSubmittedColumnValues().then((results) => {
console.log(results);
});
});
});
});
});
The biggest thing was waiting for the different calls to get done running and then waiting for the stringDataArray to be filled. That required the promise(resolve,reject) structure I found in the SO post noted above. I ended up using the lambda (()=>{}) function calls instead of declared (function(){}) for a cleaner look, the method works the same either way. None of the other proposed solutions successfully propagated the array of strings back to my test. I'm working in Typescript, with Protractor.
I'm pretty new to Protractor and I'm not satisfied with the way I wrote the code below (which is actually working).I would like to simply store the count in a variable (here, "nb_before") and reuse it after, but the promise mechanism won't allow me to do it concisely, so I decided to put everything in the same function. But it's so ugly.Am I missing something?Best regards
// We count the lines, we click on a button which adds a new line,
// then we verify that the new line has a specific text.
it("Test case", function () {
browser.get(browser.baseUrl);
var button = element(by.xpath("//whatever"));
var rows = element.all(by.xpath("//whatever"));
rows.count().then(function(count){
nb_before = count;
button.click(); // adds a line
var nb_after = nb_before + 1;
var new_line = element(by.xpath("//li[" + nb_after + "]"));
expect(new_line.getText()).toEqual("yay");
});
});
It looks like you could've used .last() method here to get the last row:
it("Test case", function () {
browser.get(browser.baseUrl);
var button = element(by.xpath("//whatever"));
var rows = element.all(by.xpath("//whatever"));
button.click(); // adds a line
expect(rows.last().getText()).toEqual("yay");
});
We can, though, get the count before and after the button click and check if it was incremented by one - this will still require us to use .then() to resolve the promise returned by .count():
var button = element(by.xpath("//whatever"));
var rows = element.all(by.xpath("//whatever"));
rows.count().then(function (countBefore) {
button.click();
expect(rows.count()).toEqual(countBefore + 1);
});
I was experimenting with nightmarejs tonight and I wanted to have a step during runtime that required user input. Is there a way to get nightmarejs to allow something like this? I modified the type() method that comes with the lib, and I was able to stop execution to enter some text. The script even finishes, but I don't see the input I typed, displayed in the text box it's supposed to go into. I'm certain the dom selector is correct because when I use the same one and the built in type() method, the static text is there. In the code below, prompt(msg) is a method from sync-prompt Here's the method I wrote, but it doesn't update the element on the page:
exports.type_async = function(selector, msg, done) {
this.page.evaluate(function(selector) {
return document.querySelector(selector);
}, function(element){
text = prompt(msg);
element.text = text;
done();
}, selector);
};
What am I doing wrong?
The original type contains the line element.value = text; in the page context (inside of evaluate, but not the result callback), but you have element.text = text;.
Also, the value should be probably set after you have prompted for the input.
exports.typeAsync = function(selector, done) {
var text = prompt('dynamic, blocking input:');
debug('.type() %s into %s', text, selector);
this.page.evaluate(function(selector, text) {
var element = document.querySelector(selector);
element.value = text;
}, done, selector, text);
};
and it's called like this:
nightmare.typeAsync('#foo')