I have a web page with a login button, to get to the site you simply click the button. This is easily done by writing this:
//Click the Login button
UITestControl Login = new UITestControl(Browser);
Login.TechnologyName = "Web";
Login.SearchProperties.Add("ControlType", "Button");
Login.SearchProperties.Add("Type", "Submit");
Login.SearchProperties.Add("DisplayText", "Log In");
Mouse.Click(Login);
HOWEVER, after you login the first time, you remain logged in for an hour (auto log out if idle for over an hour). If you access the site more than once within an hour, there will be no login button since you are still logged in so everytime I run my test, it will error right away because it won't find the control.
I hope that makes sense, here is a synopsis:
First time to site - Login screen appears, click login button, gain entry
Subsequent times to site - No login screen appears,no login required
so basically I want to say, "If there is a login button, click it then do the next thing, if no login button, then do the next thing"
There is a TryFind() method which you can use.
UITestControl Login = new UITestControl(Browser);
Login.TechnologyName = "Web";
Login.SearchProperties.Add("ControlType", "Button");
Login.SearchProperties.Add("Type", "Submit");
Login.SearchProperties.Add("DisplayText", "Log In");
// TryFind() returns true if it's in the Markup somewhere, even if hidden.
// By testing Width > 0 && Height > 0, we make sure it is visible.
// If it were Hidden and we did not use TryFind() before checking Height
// or Width, there would be an exception thrown.
if(Login.TryFind() && Login.Width > 0 && Login.Height > 0)
{
Mouse.Click(Login);
}
There is also the TryGetClickablePoint method that you can use instead of looking for width and height.
Point p;
if(Login.TryGetClickablePoint(out p))
{
Mouse.Click(Login);
}
There's a .Exists method for all UI controls
var lastPageButton = new HtmlHyperlink(parent);
lastPageButton.SearchProperties[HtmlHyperlink.PropertyNames.Title] = "Login";
lastPageButton.SearchProperties[HtmlHyperlink.PropertyNames.Class] = "YourClassHere";
if (lastPageButton.Exists) Mouse.Click(lastPageButton);
//Other work
Related
Using the HTML5 window.history API, I can control the navigation pretty well on my web app.
The app currently has two states: selectDate (1) and enterDetails (2).
When the app loads, I replaceState and set a popState listener:
history.replaceState({stage:"selectDate",...},...);
window.onpopstate = function(event) {
that.toStage(event.state.stage);
};
When a date is selected and the app moves to stage 2 I push state 2 onto the stack:
history.pushState({stage:"enterDetails",...},...);
This state is replaced anytime details change so they are saved in the history.
There are three ways to leave stage 2:
save (AJAX submit)
cancel
back button
The back button is handled by the popstate listener. The cancel button pushes stage 1 so that the user can go back to the details they were entering the back button. These both work well.
The save button should revert back to stage 1 and not allow the user to navigate back to the details page (since they already submitted). Basical, y it should make the history stack be length = 1.
But there doesn't seem to be a history.delete(), or history.merge(). The best I can do is a history.replaceState(stage1) which leaves the history stack as: ["selectDate","selectDate"].
How do I get rid of one layer?
Edit:
Thought of something else, but it doesn't work either.
history.back(); //moves history to the correct position
location.href = "#foo"; // successfully removes ability to go 'forward',
// but also adds another layer to the history stack
This leaves the history stack as ["selectDate","selectDate#foo"].
So, as an alternative, is there a way to remove the 'forward' history without pushing a new state?
You may have moved on by now, but... as far as I know there's no way to delete a history entry (or state).
One option I've been looking into is to handle the history yourself in JavaScript and use the window.history object as a carrier of sorts.
Basically, when the page first loads you create your custom history object (we'll go with an array here, but use whatever makes sense for your situation), then do your initial pushState. I would pass your custom history object as the state object, as it may come in handy if you also need to handle users navigating away from your app and coming back later.
var myHistory = [];
function pageLoad() {
window.history.pushState(myHistory, "<name>", "<url>");
//Load page data.
}
Now when you navigate, you add to your own history object (or don't - the history is now in your hands!) and use replaceState to keep the browser out of the loop.
function nav_to_details() {
myHistory.push("page_im_on_now");
window.history.replaceState(myHistory, "<name>", "<url>");
//Load page data.
}
When the user navigates backwards, they'll be hitting your "base" state (your state object will be null) and you can handle the navigation according to your custom history object. Afterward, you do another pushState.
function on_popState() {
// Note that some browsers fire popState on initial load,
// so you should check your state object and handle things accordingly.
// (I did not do that in these examples!)
if (myHistory.length > 0) {
var pg = myHistory.pop();
window.history.pushState(myHistory, "<name>", "<url>");
//Load page data for "pg".
} else {
//No "history" - let them exit or keep them in the app.
}
}
The user will never be able to navigate forward using their browser buttons because they are always on the newest page.
From the browser's perspective, every time they go "back", they've immediately pushed forward again.
From the user's perspective, they're able to navigate backwards through the pages but not forward (basically simulating the smartphone "page stack" model).
From the developer's perspective, you now have a high level of control over how the user navigates through your application, while still allowing them to use the familiar navigation buttons on their browser. You can add/remove items from anywhere in the history chain as you please. If you use objects in your history array, you can track extra information about the pages as well (like field contents and whatnot).
If you need to handle user-initiated navigation (like the user changing the URL in a hash-based navigation scheme), then you might use a slightly different approach like...
var myHistory = [];
function pageLoad() {
// When the user first hits your page...
// Check the state to see what's going on.
if (window.history.state === null) {
// If the state is null, this is a NEW navigation,
// the user has navigated to your page directly (not using back/forward).
// First we establish a "back" page to catch backward navigation.
window.history.replaceState(
{ isBackPage: true },
"<back>",
"<back>"
);
// Then push an "app" page on top of that - this is where the user will sit.
// (As browsers vary, it might be safer to put this in a short setTimeout).
window.history.pushState(
{ isBackPage: false },
"<name>",
"<url>"
);
// We also need to start our history tracking.
myHistory.push("<whatever>");
return;
}
// If the state is NOT null, then the user is returning to our app via history navigation.
// (Load up the page based on the last entry of myHistory here)
if (window.history.state.isBackPage) {
// If the user came into our app via the back page,
// you can either push them forward one more step or just use pushState as above.
window.history.go(1);
// or window.history.pushState({ isBackPage: false }, "<name>", "<url>");
}
setTimeout(function() {
// Add our popstate event listener - doing it here should remove
// the issue of dealing with the browser firing it on initial page load.
window.addEventListener("popstate", on_popstate);
}, 100);
}
function on_popstate(e) {
if (e.state === null) {
// If there's no state at all, then the user must have navigated to a new hash.
// <Look at what they've done, maybe by reading the hash from the URL>
// <Change/load the new page and push it onto the myHistory stack>
// <Alternatively, ignore their navigation attempt by NOT loading anything new or adding to myHistory>
// Undo what they've done (as far as navigation) by kicking them backwards to the "app" page
window.history.go(-1);
// Optionally, you can throw another replaceState in here, e.g. if you want to change the visible URL.
// This would also prevent them from using the "forward" button to return to the new hash.
window.history.replaceState(
{ isBackPage: false },
"<new name>",
"<new url>"
);
} else {
if (e.state.isBackPage) {
// If there is state and it's the 'back' page...
if (myHistory.length > 0) {
// Pull/load the page from our custom history...
var pg = myHistory.pop();
// <load/render/whatever>
// And push them to our "app" page again
window.history.pushState(
{ isBackPage: false },
"<name>",
"<url>"
);
} else {
// No more history - let them exit or keep them in the app.
}
}
// Implied 'else' here - if there is state and it's NOT the 'back' page
// then we can ignore it since we're already on the page we want.
// (This is the case when we push the user back with window.history.go(-1) above)
}
}
There is no way to delete or read the past history.
You could try going around it by emulating history in your own memory and calling history.pushState everytime window popstate event is emitted (which is proposed by the currently accepted Mike's answer), but it has a lot of disadvantages that will result in even worse UX than not supporting the browser history at all in your dynamic web app, because:
popstate event can happen when user goes back ~2-3 states to the past
popstate event can happen when user goes forward
So even if you try going around it by building virtual history, it's very likely that it can also lead into a situation where you have blank history states (to which going back/forward does nothing), or where that going back/forward skips some of your history states totally.
A simple solution:
var ismobilescreen = $(window).width() < 480;
var backhistory_pushed = false;
$('.editbtn').click( function()
{
// push to browser history, so back button will close the editor
// and not navigate away from site
if (ismobilescreen && !backhistory_pushed)
{
window.history.pushState('forward', null, window.location);
backhistory_pushed = true;
}
}
Then:
if (window.history && window.history.pushState)
{
$(window).on('popstate', function()
{
if (ismobilescreen && backhistory_pushed && $('.editor').is(':visible'))
{
// hide editor window (we initiate a click on the cancel button)
$('.editor:visible .cancelbtn').click();
backhistory_pushed = false;
}
});
}
Results in:
User opens editor DIV, the history state is saved.
User hits back button, history state is taken into account.
Users stays on page!
Instead of navigating back, the editor DIV is closed.
One issue: If you use a "Cancel" button on your DIV and this hides the editor, then the user has to click the mobile's back button two times to go back to the previous URL.
To solve this problem you can call window.history.back(); to remove the history entry by yourself which actually deletes the state as requested.
For example:
$('.btn-cancel').click( function()
{
if (ismobilescreen && backhistory_pushed)
{
window.history.back();
}
}
Alternatively you could push a URL into the history that holds an anchor, e.g. #editor and then push to history or not if the anchor exists in the recent URL or not.
How do I set "onDemand" as default when the form is loaded?
(Background: There is a hidden text box which should only be visible when "Predefine" is checked. Unfortunately, it is visible when the form gets loaded first. At this point I thought about making the onDemand checked by default to keep the textbox invisbile at first page load)
{"key":"discountType","type":"radioType", "templateOptions":{"options": [{"name":"OnDemand","value":"OnDemand"},{"name":"Predefined","value":"Predefine"}]}},
{"key":"discValue","type":"input","templateOptions": {"type":"input"}, hideExpression: "model.discountType=='OnDemand'"}
]
}
Did some trial and error after sleeping over it and I got it.
discount.discountInfo.discType="OnDemand"; resolved it.
var discount = this;
discount.title = title;
discount.discountInfo = {};
discount.fields=[{"key":"discountType","type":"radioType", "templateOptions":{"options": [{"name":"OnDemand","value":"OnDemand"},{"name":"Predefined","value":"Predefine"}]}},
{"key":"discValue","type":"input","templateOptions": {"type":"input"}, hideExpression: "model.discountType=='OnDemand'"}
]
discount.discountInfo.discType="OnDemand";
I want to be able to go to options menu in the game I am developing and set up my controls.
It is a simple game of pong (for now) and the controls for each player are just up and down.
This is how I want the process to look like: I click SETUP CONTROLS, game displays the name of the control I am supposed to change and it waits, I click the button on keyboard that i want it to be changed to, game reads it and displays the next control I am supposed to change and so on until i change all controls.
I have found a way how to do that here and my code now looks basicly like this:
if (optionsBList.IsButtonClicked("SETUP CONTROLS")) //when i click the
//SETUP CONTROLS button
//in the options menu
{
KeyboardState currentKeyboardState = new KeyboardState();
waitingForKey = true;
while (waitingForKey)
{
if(currentKeyboardState.GetPressedKeys().Count() > 0)
{
player1.upkey = currentKeyboardState.GetPressedKeys()[0];
//the path for the key isn't player1.upkey, but lets say it is.
waitingForKey = false;
}
}
}
In this short code my goal is to change just one key. If I can make it change 1 key, changing more wont be a problem.
The problem is, I don't see why does my game stop responding when i click the SETUP CONTROLS button. I don't see an infinite loop here nor a memory leak.
Why is my game crashing and is there a better way to load controls in options menu?
If you saw the answer from the link you placed in your question, you would have noticed that he actually deleted the while loop and he made it an if statement.
Like Nico Schertler said : "while(waitingForKey) is your infinite loop. That loop blocks the game thread, so no further input is recognized."
Link to the answer from your link: https://stackoverflow.com/a/15935732/3239917
if (optionsBList.IsButtonClicked("SETUP CONTROLS")) //when i click the
//SETUP CONTROLS button
//in the options menu
{
KeyboardState currentKeyboardState = new KeyboardState();
waitingForKey = true;
if (waitingForKey )
{
if(currentKeyboardState.GetPressedKeys().Count() > 0)
{
player1.upkey = currentKeyboardState.GetPressedKeys()[0];
//the path for the key isn't player1.upkey, but lets say it is.
waitingForKey = false;
}
}
}
I have a xpage that loads a Bootstrap modal window (see picture). I need to validate the data only the user pushes the 'Add New' button. I do not want the validators to run when 'Save and Continue' or 'Previous' buttons are pressed.
The modal dialog is a custom control used many times throughout the application. I cannot change the buttons to disable validators because most of the time I want to validate. The center content is stored in its own custom control.
My question is can I add code to the button to get a handle to each control, and set the disableValidators to true. I haven't been able to figure out how to do this. I have already tried computing the disableValidators but was totally unsuccessful. The fields are all bound to scoped variables.
I know that I can use clientside validation here, but the rest of the application uses server-side and I want to be consistent + this is a responsive app, and clientside on mobile is annoying.
Rather than adding code to button to and setting disableValidators for each control I would suggest the other way round. For each control set the required property only if user pushes 'Add New' button.
This blog post by Tommy Valand describes it in detail. The below function lets you test if a specific component triggered an update.
// Used to check which if a component triggered an update
function submittedBy( componentId ){
try {
var eventHandlerClientId = param.get( '$$xspsubmitid' );
var eventHandlerId = #RightBack( eventHandlerClientId, ':' );
var eventHandler = getComponent( eventHandlerId );
if( !eventHandler ){ return false; }
var parentComponent = eventHandler.getParent();
if( !parentComponent ){ return false; }
return ( parentComponent.getId() === componentId );
} catch( e ){ /*Debug.logException( e );*/ }
}
So if you want the validation to run only when the user clicks a specific 'Add New' button then write this in all the required-attributes:
return submittedBy('id-of-add-new-button')
In short:
I want to show a view or action sheet and only continue code execution after the user has dismissed the view / sheet. So: line one shows the view, line two reads some result variable.
In detail why I would need this:
I'm porting a Windows Forms application over to the iPad. The original implementation has a communication class which uses a web service to communicate with the server. It offers a couple of methods to get data. Conveniently it checks prior to each call if the user still has a valid connection or if he has to re-enter his password for security reasons.
If the password is required, the .NET class shows a modal dialog which blocks any further code executio and if the password was entered, retries the last call it has made before showing the dialog.
Now using CocoaTouch I'm facing a problem. I replaced the code that shows the dialog with a UIActionSheet. Works great but code execution continues immediately, whereas in Windows Forms it is blocked (the next line in Windows Forms after showing the dialogs is to read the entered password from the dialog) until the dialog has been closed.
I tried a Thread.Sleep() until the user dismisses the UIActionSheet but the Thread.Sleep() also blocks the main loop and my view won't even be drawn.
The alternative I currently see is to change all methods in the already working class and give them a return value: if password required, handle it, then retry.
But this means that all over my code I will have to add these checks because at any given moment the password might be needed. That's why it is nested in communication class in Windows Forms.
Any other ideas?
René
Yes, it is possible.
To do this, what you can do is to run the mainloop manually. I have not managed to stop the mainloop directly, so I instead run the mainloop for 0.5 seconds and wait until the user responds.
The following function shows how you could implement a modal query with the above approach:
int WaitForClick ()
{
int clicked = -1;
var x = new UIAlertView ("Title", "Message", null, "Cancel", "OK", "Perhaps");
x.Show ();
bool done = false;
x.Clicked += (sender, buttonArgs) => {
Console.WriteLine ("User clicked on {0}", buttonArgs.ButtonIndex);
clicked = buttonArgs.ButtonIndex;
};
while (clicked == -1){
NSRunLoop.Current.RunUntil (NSDate.FromTimeIntervalSinceNow (0.5));
Console.WriteLine ("Waiting for another 0.5 seconds");
}
Console.WriteLine ("The user clicked {0}", clicked);
return clicked;
}
I think this approach using async/await is much better, and doesn't suffer from freezing the app when rotating the device, or when the autoscrolling interferes and leaves you stuck in the RunUntil loop forever without the ability to click a button (at least these problems are easy to reproduce on iOS7).
Modal UIAlertView
Task<int> ShowModalAletViewAsync (string title, string message, params string[] buttons)
{
var alertView = new UIAlertView (title, message, null, null, buttons);
alertView.Show ();
var tsc = new TaskCompletionSource<int> ();
alertView.Clicked += (sender, buttonArgs) => {
Console.WriteLine ("User clicked on {0}", buttonArgs.ButtonIndex);
tsc.TrySetResult(buttonArgs.ButtonIndex);
};
return tsc.Task;
}