I am trying to set the value of the input field using the devtools console in Google Chrome
The catch to this task is that the element's state is likely to be contolled by the React.js state. I have made this conclusion after finding the property that says __reactProps, among the others:
The element that I am trying to manipulate is located at TradingView. If you open any king of asset and find the Calendar button on the bottom on the page:
A modal window will open with following contents:
I am trying to programmatically set the date input field value to any other valid value, but the problem is, that after submitting the form - the old value is being used! In fact, this could be further confirmed by hovering the mouse cursor over the element before the form submit. After doing so - the value will be reverted to an old/initial value.
I have made a dozen of attempts to permanently change the value from the devtools console, but it keeps reverting back to it's initial value no matter what:
var myObj = document.querySelector("div[data-name='go-to-date-dialog'] input");
myObj.value = "2021-09-01";
myObj.setAttribute('value', "2021-09-01");
myObj.defaultValue = '2021-09-01';
myObj._wrapperState.initialValue = '2021-09-01';
var keys = Object.keys(myObj);
var propsKey = keys.find(key=>{ return key.startsWith("__reactProps$") });
myObj[propsKey].value = '2021-09-01';
myObj.dispatchEvent(new Event('blur'));
Does React.js require some kind of special treatment? Is there a way to invoke the new state into the date component from the console and a vanilla js?
The task that I am trying to achieve is done for educational purposes only and is not meant to "hack" or bring harm to any particular software or platform. I would assume this question is relevant to ANY component made by React.js on any website.
You have to create an input event and set the value using the event:
var input = document.querySelector("div[data-name='go-to-date-dialog'] input");
var nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
nativeInputValueSetter.call(input, '2021-09-01');
var ev2 = new Event('input', { bubbles: true});
input.dispatchEvent(ev2);
Related
Google Apps Script supports Triggers, that pass Events to trigger functions. Unfortunately, the development environment will let you test functions with no parameter passing, so you cannot simulate an event that way. If you try, you get an error like:
ReferenceError: 'e' is not defined.
Or
TypeError: Cannot read property *...* from undefined
(where e is undefined)
One could treat the event like an optional parameter, and insert a default value into the trigger function using any of the techniques from Is there a better way to do optional function parameters in JavaScript?. But that introduces a risk that a lazy programmer (hands up if that's you!) will leave that code behind, with unintended side effects.
Surely there are better ways?
You can write a test function that passes a simulated event to your trigger function. Here's an example that tests an onEdit() trigger function. It passes an event object with all the information described for "Spreadsheet Edit Events" in Understanding Events.
To use it, set your breakpoint in your target onEdit function, select function test_onEdit and hit Debug.
/**
* Test function for onEdit. Passes an event object to simulate an edit to
* a cell in a spreadsheet.
*
* Check for updates: https://stackoverflow.com/a/16089067/1677912
*
* See https://developers.google.com/apps-script/guides/triggers/events#google_sheets_events
*/
function test_onEdit() {
onEdit({
user : Session.getActiveUser().getEmail(),
source : SpreadsheetApp.getActiveSpreadsheet(),
range : SpreadsheetApp.getActiveSpreadsheet().getActiveCell(),
value : SpreadsheetApp.getActiveSpreadsheet().getActiveCell().getValue(),
authMode : "LIMITED"
});
}
If you're curious, this was written to test the onEdit function for Google Spreadsheet conditional on three cells.
Here's a test function for Spreadsheet Form Submission events. It builds its simulated event by reading form submission data. This was originally written for Getting TypeError in onFormSubmit trigger?.
/**
* Test function for Spreadsheet Form Submit trigger functions.
* Loops through content of sheet, creating simulated Form Submit Events.
*
* Check for updates: https://stackoverflow.com/a/16089067/1677912
*
* See https://developers.google.com/apps-script/guides/triggers/events#google_sheets_events
*/
function test_onFormSubmit() {
var dataRange = SpreadsheetApp.getActiveSheet().getDataRange();
var data = dataRange.getValues();
var headers = data[0];
// Start at row 1, skipping headers in row 0
for (var row=1; row < data.length; row++) {
var e = {};
e.values = data[row].filter(Boolean); // filter: https://stackoverflow.com/a/19888749
e.range = dataRange.offset(row,0,1,data[0].length);
e.namedValues = {};
// Loop through headers to create namedValues object
// NOTE: all namedValues are arrays.
for (var col=0; col<headers.length; col++) {
e.namedValues[headers[col]] = [data[row][col]];
}
// Pass the simulated event to onFormSubmit
onFormSubmit(e);
}
}
Tips
When simulating events, take care to match the documented event objects as close as possible.
If you wish to validate the documentation, you can log the received event from your trigger function.
Logger.log( JSON.stringify( e , null, 2 ) );
In Spreadsheet form submission events:
all namedValues values are arrays.
Timestamps are Strings, and their format will be localized to the Form's locale. If read from a spreadsheet with default formatting*, they are Date objects. If your trigger function relies on the string format of the timestamp (which is a Bad Idea), take care to ensure you simulate the value appropriately.
If you've got columns in your spreadsheet that are not in your form, the technique in this script will simulate an "event" with those additional values included, which is not what you'll receive from a form submission.
As reported in Issue 4335, the values array skips over blank answers (in "new Forms" + "new Sheets"). The filter(Boolean) method is used to simulate this behavior.
*A cell formatted "plain text" will preserve the date as a string, and is not a Good Idea.
Update 2020-2021:
You don't need to use any kind of mocks events as suggested in the previous answers.
As said in the question, If you directly "run" the function in the script editor, Errors like
TypeError: Cannot read property ... from undefined
are thrown. These are not the real errors. This error is only because you ran the function without a event. If your function isn't behaving as expected, You need to figure out the actual error:
To test a trigger function,
Trigger the corresponding event manually: i.e., To test onEdit, edit a cell in sheet; To test onFormSubmit, submit a dummy form response; To test doGet, navigate your browser to the published webapp /exec url.
If there are any errors, it is logged to stackdriver. To view those logs,
In Script editor > Execution icon on the left bar(Legacy editor: View > Executions).
Alternatively, Click here > Click the project you're interested in > Click "Executions" icon on the left bar(the 4th one)
You'll find a list of executions in the executions page. Make sure to clear out any filters like "Ran as:Me" on the top left to show all executions. Click the execution you're interested in, it'll show the error that caused the trigger to fail in red.
Note: Sometimes, The logs are not visible due to bugs. This is true especially in case of webapp being run by anonymous users. In such cases, It is recommended to Switch Default Google cloud project to a standard Google cloud project and use View> Stackdriver logging directly. See here for more information.
For further debugging, You can use edit the code to add console.log(/*object you're interested in*/) after any line you're interested in to see details of that object. It is highly recommended that you stringify the object you're looking for: console.log(JSON.stringify(e)) as the log viewer has idiosyncrasies. After adding console.log(), repeat from Step 1. Repeat this cycle until you've narrowed down the problem.
Congrats! You've successfully figured out the problem and crossed the first obstacle.
2017 Update:
Debug the Event objects with Stackdriver Logging for Google Apps Script. From the menu bar in the script editor, goto:
View > Stackdriver Logging to view or stream the logs.
console.log() will write DEBUG level messages
Example onEdit():
function onEdit (e) {
var debug_e = {
authMode: e.authMode,
range: e.range.getA1Notation(),
source: e.source.getId(),
user: e.user,
value: e.value,
oldValue: e. oldValue
}
console.log({message: 'onEdit() Event Object', eventObject: debug_e});
}
Example onFormSubmit():
function onFormSubmit (e) {
var debug_e = {
authMode: e.authMode,
namedValues: e.namedValues,
range: e.range.getA1Notation(),
value: e.value
}
console.log({message: 'onFormSubmit() Event Object', eventObject: debug_e});
}
Example onChange():
function onChange (e) {
var debug_e = {
authMode: e.authMode,
changeType: changeType,
user: e.user
}
console.log({message: 'onChange() Event Object', eventObject: debug_e});
}
Then check the logs in the Stackdriver UI labeled as the message string to see the output
As an addition to the method mentioned above (Update 2020) in point 4.:
Here is a small routine which I use to trace triggered code and that has saved me a lot of time already. Also I have two windows open: One with the stackdriver (executions), and one with the code (which mostly resides in a library), so I can easily spot the culprit.
/**
*
* like Logger.log %s in text is replaced by subsequent (stringified) elements in array A
* #param {string | object} text %s in text is replaced by elements of A[], if text is not a string, it is stringified and A is ignored
* #param {object[]} A array of objects to insert in text, replaces %s
* #returns {string} text with objects from A inserted
*/
function Stringify(text, A) {
var i = 0 ;
return (typeof text == 'string') ?
text.replace(
/%s/g,
function(m) {
if( i >= A.length) return m ;
var a = A[i++] ;
return (typeof a == 'string') ? a : JSON.stringify(a) ;
} )
: (typeof text == 'object') ? JSON.stringify(text) : text ;
}
/* use Logger (or console) to display text and variables. */
function T(text) {
Logger.log.apply(Logger, arguments) ;
var Content = Stringify( text, Array.prototype.slice.call(arguments,1) ) ;
return Content ;
}
/**** EXAMPLE OF USE ***/
function onSubmitForm(e) {
T("responses:\n%s" , e.response.getItemResponses().map(r => r.getResponse()) ;
}
I am using the Table object from the SAPUI5's sap.ui.table namespace:
var oTableOverview = new sap.ui.table.Table();
On rowSelectionChange, when selecting one row I populate another table, let's call it oTableDetail, that is filled with some data.
When deselecting the row from the first table, I want to clear the content of the second, and for that I use:
oTableDetail.destroyColumns();
oTableDetail.unbindRows();
When deselecting the row I get the following error:
TableRenderer.js:6 Uncaught TypeError: Cannot read property 'shouldRender' of undefined
I found the method shouldRender of the sap.ui.table.Column class, but I am not sure why would the cells be rerendered in this case.
I also noticed that if I use either oTable.destroyColumns(), or oTable.unbindRows() separately, the error does not appear.
I am using the "1.38.11" version of SAPUI5.
Can you please help me identify why this happens?
EDIT 1: A possible workaround would be to use:
oTableDetail.setModel(new sap.ui.model.json.JSONModel({}));
oTableDetail.destroyColumns();
Although I still don't know why the code mentioned before is not working.
EDIT 2: A behavior that I find a bit weird:
trying to add a setTimeout like this works (the error is not happening):
oTable.destroyColumns();
setTimeout(function(){ oTable.unbindRows(); }, 50);
but this other way it doesn't work (the error still appears) even if the delay is longer:
oTable.unbindRows();
setTimeout(function(){ oTable.destroyColumns(); }, 50);
var oTableOverview = new sap.ui.table.Table();
//your model with new dynamic data
var oModel = new sap.ui.model.json.JSONModel(newData);
oTableDetail.setModel(oModel);
//use rerender method which tries to replace
//its DOM reference by re-rendering with new data.
oTableDetail.rerender();
You don't need to actually delete columns or unbind rows.
Rerender method will take care of new data.
So if you don't want to set a new model, then just try to clean oData of an existing model.
var oTable = this.byId("oTable")
//your model with new dynamic data
var oModel = oTable.getModel();
oModel.setData(null);
// then if its needed, use updateBindings method to refresh
oModel.updateBindings();
In one of our tests, we need to make sure that the tab keyboard navigation inside a form is performed in the correct order.
Question: What is the conventional way to check the tab navigation order with protractor?
Currently we are solving it by repeating the following step for as many input fields existing in a form (code below):
check the ID of the currently focused element (using getId())
send TAB key to the currently focused element
Here is the example spec:
it("should navigate with tab correctly", function () {
var regCodePage = new RegCodePage();
browser.wait(protractor.ExpectedConditions.visibilityOf(regCodePage.title), 10000);
// registration code field has focus by default
expect(regCodePage.registrationCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
// focus moved to Remember Registration Code
regCodePage.registrationCode.sendKeys(protractor.Key.TAB);
expect(regCodePage.rememberRegistrationCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
// focus moved to Request Code
regCodePage.rememberRegistrationCode.sendKeys(protractor.Key.TAB);
expect(regCodePage.requestCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
// focus moved to Cancel
regCodePage.requestCode.sendKeys(protractor.Key.TAB);
expect(regCodePage.cancelButton.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
// focus moved back to the input
regCodePage.cancelButton.sendKeys(protractor.Key.TAB);
expect(regCodePage.registrationCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
});
where regCodePage is a Page Object:
var RegCodePage = function () {
this.title = element(by.css("div.modal-header b.login-modal-title"));
this.registrationCode = element(by.id("regCode"));
this.rememberRegistrationCode = element(by.id("rememberRegCode"));
this.requestCode = element(by.id("forgotCode"));
this.errorMessage = element(by.css("div.auth-reg-code-block div#message"));
this.sendRegCode = element(by.id("sendRegCode"));
this.cancelButton = element(by.id("cancelButton"));
this.closeButton = element(by.css("div.modal-header button.close"));
};
module.exports = RegCodePage;
It is working, but it is not really explicit and readable which makes it difficult to maintain. Also, another "smell" in the current approach is a code duplication.
If the current approach is how you would also do it, I would appreciate any insights about making it reusable.
I think the PageObject should define a tab order list, since that is really a direct property of the page, and should be expressible as simple data. An array of items seems like a sufficient representation, so something like:
this.tabOrder = [ this.registrationCode, this.rememberRegistrationCode, this.requestCode, this.cancelButton ];
Then you need a bit of generic code that can check a tab order.
function testTabOrder(tabOrder) {
// Assumes TAB order hasn't been messed with and page is on default element
tabOrder.forEach(function(el) {
expect(el.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
el.sendKeys(protractor.Key.TAB);
});
}
Then your test would be something like:
it('has correct tab order', function() {
var regCodePage = new RegCodePage(); // this should probably be in the beforeEach
testTabOrder(regCodePage.tabOrder);
});
Of course, this assumes each element has a "getId()" method that works. (That seems like a reasonable assumption to me, but some environments may not support it.)
I think this keeps the tab-order nicely isolated on the PageObject (so its easy to keep in sync with the page content and doesn't get lost in the code that verifies the order). The testing code seem "optimistic" (I suspect the real world will introduce enough problems that you will end up expanding this code a bit).
I haven't tried any of this yet, so feel free to downvote if this doesn't work. :)
Also, I believe the forEach loop will work as-is, but I wouldn't be surprised if it needs some more explicit promise handling to make the dependencies explicit.
I am working on an angularJS app and i found a problem that I cant solve. I have a variable with predefined text that I want to replace in an email with the actual values, it looks like this:
$scope.predefinedVars = {
'client_name': $scope.itemPartner.name,
'client_city': $scope.itemPartner.city,
'client_county': $scope.itemPartner.county,
'client_address': $scope.itemPartner.address,
'client_phone': $scope.itemPartner.phone,
'client_email': $scope.itemPartner.email
};
and so on...
Now later on, when I choose a partner, the itemPartner object changes the data. For example: i have a function to watch when I change the partner from a select box:
$scope.$watch('newContract.partner_id', function() {
$scope.itemPartner = _.where($scope.listPartners, {'id': $scope.newContract.partner_id})[0];
alert(JSON.stringify($scope.itemPartner));
});
Now, in the alert, I can see the itemPartner data has changed, but if I try to read the values from my 1st variable $scope.predefinedVars, the values are still empty and do not change.
Is there a way to force the values to change when I change the itemPartner object ?
The problem is that you have set the fields of your predefinedVars object to a primitive. In javascript, primitives are passed by value, not by reference (google if you're not sure what that means).
The result is that any reference to the object you used to set it originally is lost.
You have a couple of alternatives:
Instead of replacing the email using the data from $scope.predefinedVars, use the data from $scope.itemPartner. This is less work.
create a function that repopulates predefinedVars
e.g.
function populatePredefinedVars (partner){
$scope.predefinedVars = {
'client_name': partner.name,
'client_city': partner.city,
'client_county': partner.county,
'client_address': partner.address,
'client_phone': partner.phone,
'client_email': partner.email
};
}
$scope.$watch('newContract.partner_id', function() {
$scope.itemPartner = _.where($scope.listPartners, {'id': $scope.newContract.partner_id})[0];
populatePredefinedVars($scope.itemPartner);
});
I have a editable grid, store and a button.
The button has a handler that is supposed to copy the selected record and add the copy to the store:
var a = gridPanel.getSelectionModel().getSelectedCell();
var rec = store.getAt(a[0]).copy();
store.addSorted(rec);
alert (store.getAt(1).get('date'));
But the store and the grid are not updated. The alert has an error - cannot call method of undefined.
What is the problem here?
The issue probably is that the copied record has the same id thus when you insert it in the store another record with the same id is already present.
If you generate and apply a new id to the copied record before adding it to the store it should work. The following code generates a new ID in the record passed as argument. Check the documentation of Ext.data.Record.copy.
Ext.data.Record.id(rec);
Few things that most JavaScript developers should do:
Use firebug, if you turn on break on all errors it would probably show you that store.get(1) is returning undefing and causing an error when you try to call a function on an undefined.
Now that you have firebug use console.log() statements over window.alert(). With console.log's you can actually see the an inspect it, it is also good for asynchronous stuff and mouse events.
As for your problem:
Calling a record.copy() and then inserting it into the store will cause problems if you don't give it an id. If you had firebug and looked through the code you would stumble upon this:
if(this.containsKey(key)){
this.suspendEvents();
this.removeKey(key);
this.resumeEvents();
}
To generate a record with a unique key you can do something like this :
var rec = store.getAt(a[0]).copy();
var id = Ext.data.Record.id(id);
rec.id = id;
The code seems messy but there aren't many nice ways of doing it. If it were me I would override the copy function to take a boolean to force the record's id to be auto generated.