I am trying to automate the login form of Instagram web app:
https://instagram.com/accounts/login/
With the following code (you can run it on Chrome console):
var frm = window.frames[1].document.forms[0];
frm.elements[0].value = 'qacitester';
frm.elements[1].value = 'qatester';
frm.elements[2].click();
Even though the inputs are populated, when I monitor the XHR request, I see this is posted:
username=&password=&intent=
rather than this:
username=qacitester&password=qatester&intent=
And causes web app to not authenticate. Do you have any idea why the input values are not transferred to the backing state (model?) object of React?
State updates in React are asynchronous; when an update is requested, there is no guarantee that the updates will be made immediately. The updater functions enqueue changes to the component state, but React may delay the changes, updating several components in a single pass.
State can hold any kind of JavaScript value, including objects. But you shouldn't change objects that you hold in the React state directly. Instead, when you want to update an object, you need to create a new one (or make a copy of an existing one), and then set the state to use that copy.
React do not update immediately, although it seems immediate at first glance.
getDerivedStateFromProps(props, state) is a static method that is called just before render() method in both mounting and updating phase in React. It takes updated props and the current state as arguments. We have to return an object to update state or null to indicate that nothing has changed.
Instagram is using the linkState
method from ReactLink:
http://facebook.github.io/react/docs/two-way-binding-helpers.html
The login page links to a bundles/Login.js
that contains compressed code like this:
o.createElement("input", {
className:"lfFieldInput",
type:"text",
name:"username",
maxLength:30,
autoCapitalize:!1,
autoCorrect:!1,
valueLink:this.linkState("username")
}))
If you look at the documentation for ReactLink, this means that the input field value only gets sent back to state when the change event fires on the input fields. However, it's not as simple as just firing the change event on the node in question - I think due to the way that React normalises browser events. The React docs specifically say that onChange in React isn't exactly the same as onChange in the browser for various reasons:
http://facebook.github.io/react/docs/dom-differences.html
Fortunately in your case Instagram are using React with Addons included which gives you access to React TestUtils, so you can do:
var frm = window.frames[1].document.forms[0];
var fReact = window.frames[1].React;
fReact.addons.TestUtils.Simulate.change(frm.elements[0], {target: {value: 'qacitester' }});
fReact.addons.TestUtils.Simulate.change(frm.elements[1], {target: {value: 'qatester' }});
frm.elements[2].click();
More documentation here:
http://facebook.github.io/react/docs/test-utils.html
Notice that since the login form itself is in an iframe, you must use the instance of React from that iframe otherwise the TestUtils won't be able to find the nodes correctly.
This will only work in situations where React Addons are included on the page. The normal non-Addons build of React will require another solution. However, if you're specifically talking about ReactLink then that's an addon anyway, so it's not an issue.
Answering my own question, the required event is input event it seems and this code works:
var doc = window.frames[1].document;
var frm = doc.forms[0];
frm.elements[0].value = 'qacitester';
var evt = doc.createEvent('UIEvent');
evt.initUIEvent('input', true, false);
frm.elements[0].dispatchEvent(evt);
frm.elements[1].value = 'qatester';
frm.elements[1].dispatchEvent(evt);
frm.elements[2].click();
The login form is actually inside an iFrame, which makes things a wee bit more complicated. I was able to successfully submit a login running the following code from the chrome console. I'm using React's TestUtils, but you may be fine with AutoType:
// run in Chrome console
var frame = document.querySelector('iframe[src*="login"]');
var s = document.createElement('script');
var username = frame.contentDocument.querySelector('input[name="username"]');
var password = frame.contentDocument.querySelector('input[name="password"]');
s.src = "https://fb.me/react-with-addons-0.12.0.js";
frame.contentDocument.body.appendChild(s);
// wait for the script request to resolve and use:
change = frame.contentWindow.React.addons.TestUtils.Simulate.change;
change(username, {target: {value: 'My Great Username'}});
change(password, {target: {value: 'securepassword'}});
Background
React uses its own virtual event system, and typically uses controlled components, which essentially means the input value
is set by react and trickles down to DOM element.
With that in mind, I believe the issue you are experiencing is because react looks for a key event on the input, and uses the value of the input at the time of the event to set the value of the DOM element. Since you are just setting the value
property of the DOM element, you are breaking out of the controlled component cycle.
I did a quick spike attempting to dispatch a KeyboardEvent
on the input, but it was, alas, unsuccessful. I'll update my answer if indeed I do succeed.
Non satisfactory workaround:
Use casper/phantomjs to automate form filling. This means you don't have to worry about dispatching events yourself and you can just let the "browser" do it for you. It's also better than working in the console manually.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With