I tried two PickerComponents inside a TextModeLayout with the simulator (with an Android skin). I don't like that a PickerComponent of Strings shows three dots on unselected state. I also don't like that the PickerComponent of a Date shows the today date. In both cases, I want to show a custom text, also because I don't want a default selected value. For example, if I want to pick the date of birth, I don't feel that it makes sense to preset the today date.
After a lot of trials, I tried to solve this issue with the following code, but I'm not sure if it's a correct way. My question is what is the best approch in a portable manner (that is ok for Android and for iOS):
/**
* Set a custom text for an unselected PickerComponent placed in a
* TextModeLayout
*
* #param picker
* #param text
*/
private void pickerComponentSetUnselectedText(PickerComponent picker, String text) {
picker.getPicker().setText(text);
picker.getPicker().setUIID("TextHint");
picker.getPicker().addActionListener(l -> {
l.getComponent().setUIID("TextField");
});
}
I tried to use that method so:
TextModeLayout textModeLayout = new TextModeLayout(4, 1);
Container inputPersonData = new Container(textModeLayout);
TextComponent name = new TextComponent().label("Nome");
TextComponent surname = new TextComponent().label("Cognome");
PickerComponent gender = PickerComponent.createStrings("Maschio", "Femmina", "altro").label("Genere");
PickerComponent date = PickerComponent.createDate(new Date()).label("Data di nascita");
inputPersonData.add(name);
inputPersonData.add(surname);
inputPersonData.add(gender);
inputPersonData.add(date);
pickerComponentSetUnselectedText(gender, "Genere");
pickerComponentSetUnselectedText(date, "Data di nascita");
The picker component has two pieces, the one with the text isn't native but the popup is (and that's the source of most of the problems).
If what you did worked go with it. Historically we would recommend subclassing picker and overriding updateValue but it's impossible to do with PickerComponent so I added a new method which should be available in the next update:
PickerComponent cmp = new PickerComponent() {
protected Picker createPickerInstance() {
return new Picker() {
protected void updateValue() {
// place your logic here.. and invoke setText(...);
}
};
}
};
Related
I noticed that after an update of CN1 some time ago, the Search doesn't display correctly in the toolbar anymore.
When you click the search icon, the toolbar will change and the icons will show, but the textfield for entering the search text (and which normally shows a hint) doesn't show up even if you start to type. It is only shown when you provoke a refresh of the screen, e.g. click in the search field or on the form.
Form hi18 = new Form("FormTitle");
hi18.setLayout(BoxLayout.y());
Container cont18 = hi18.getContentPane();
hi18.getToolbar().addSearchCommand((e) -> {
String text = (String) e.getSource();
for (Component c : hi18.getContentPane()) {
c.setHidden(c instanceof Label && ((Label) c).getText().indexOf(text) < 0);
}
hi18.getComponentForm().animateLayout(150);
});
for (int i = 0; i < 20; i++) {
Label l = new Label("Label " + i);
cont18.add(l);
}
hi18.show();
Looks fine for me considering you created a title with no text in it for the test case so it might be shrinking on you. I suggest adding text to the title and making sure your styling doesn't impact this too much. I would also suggest adding screenshots when discussing something that doesn't look right so we're all on the same page.
These were generated with your code on the current trunk:
I'm new to Swift and followed a simple tutorial to make a magic 8 ball Cocoa App that every time I click the ball it shows a different piece of advice. I am now trying to practice my UI automated tests by asserting (XCTAssert) that the "Piece of Advice" label is equal to one of the string values in my array.
My array looks like this and is in my ViewController.swift:
var adviceList = [
"Yes",
"No",
"Tom says 'do it!'",
"Maybe",
"Try again later",
"How can I know?",
"Totally",
"Never",
]
How can I make an assertion in my UITests.swift file that asserts that the string that is shown is equal to one of the string values in the array above?
It's possible that you're asking how to access application state from a UI test, or just in general UI testing.
I think it's a pretty interesting question so I'm going to answer because it's something that I don't know a lot about and hopefully will prompt other people to chime in and correct.
Background: A basic Magic 8 Ball project
I set up a basic project with a view controller that contains two views: a label and a button. Tapping the button updates the label text with a random message:
import UIKit
struct EightBall {
static let messages = ["Yes", "No", "It's not certain"]
var newMessage: String {
let randomIndex = Int(arc4random_uniform(UInt32(EightBall.messages.count)))
return EightBall.messages[randomIndex]
}
}
class ViewController: UIViewController {
let ball = EightBall()
#IBOutlet weak var messageLabel: UILabel!
#IBAction func shakeBall(_ sender: Any) {
messageLabel.text = ball.newMessage
}
}
A basic UI test
Here's a commented UI test showing how to automate tapping on the button, and grabbing the value of the label, and then checking that the value of the label is a valid message.
import XCTest
class MagicUITests: XCTestCase {
// This method is called before the invocation of each test method in the class.
override func setUp() {
super.setUp()
// In UI tests it is usually best to stop immediately when a failure occurs.
continueAfterFailure = true
// UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
XCUIApplication().launch()
}
func testValidMessage() {
// Grab reference to the application
let app = XCUIApplication()
// #1
// Grab reference to the label with the accesability identifier 'MessageLabel'
let messagelabelStaticText = app.staticTexts["MessageLabel"]
// Tap the button with the text 'Shake'
app.buttons["Shake"].tap()
// get the text of the label
let messageLabelText = messagelabelStaticText.label
// #2
// check if the text in the label matches one of the allowed messages
let isValidMessage = EightBall.messages.contains(messageLabelText)
// test will fail if the message is not valid
XCTAssert(isValidMessage)
}
}
At #1 The approach that I'm using to get the label is to access the labels accessibilityIdentifier property. For this project I entered this through storyboard, but if you're setting your views up in code you can directly set the accessibilityIdentifier property yourself.
The other thing that's confusing here is that to get access to elements in the view you're not navigating the view hierarchy, but a proxy of the hierarchy, which is why the syntax to get a label is the odd 'staticTexts' (The references at the bottom of the post explain this in more detail).
For #2 I'm inspecting the structure defined in my project. In a unit test you could access this my importing #testable import ProjectName but unfortunately this approach doesn't work for UI Test.
Instead, you'll have to make sure that any source file you want to access from the UI test is included as a target. You can do this in Xcode from this panel by checking the name of your UI test:
More UI testing references:
UI Testing Intro: http://www.mokacoding.com/blog/xcode-7-ui-testing/
UI Testing Cheat Sheet: http://masilotti.com/ui-testing-cheat-sheet/
At this moment I'm only testing my app in the simulator (as I'm having issues with "Send iOS Build" mentioned in another thread [Errors with Codename One "Send iOS Build" and "Send Android Build")
I'm experiencing some layout issues where it is not making use of the width and height correctly. The elements are left-aligned and there is unused space on the right side. And I need to scroll up and down instead of having everything fit within the visual area. Please see images.
The code are:
private final void show() {
loginSignupForm = new Form("Company", new BoxLayout(0));
Tabs loginSignupTabs = new Tabs();
Style loginSignupStyle = UIManager.getInstance().getComponentStyle("Tab");
prepareAndAddSignupTab(loginSignupTabs, loginSignupStyle);
prepareAndAddLoginTab(loginSignupTabs, loginSignupStyle);
loginSignupForm.add(loginSignupTabs);
loginSignupForm.show();
}
private void prepareAndAddLoginTab(Tabs loginSignupTabs, Style loginSignupStyle) {
loginID = new TextField();
loginPassword = new TextField();
Button loginButton = getLoginButton();
Component[] loginComponents = {
new Label("Email Address"),
loginID,
new Label("Password"),
loginPassword,
loginButton,
};
Container loginContainer = BoxLayout.encloseY(loginComponents);
FontImage loginIcon = FontImage.createMaterial(FontImage.MATERIAL_QUESTION_ANSWER, loginSignupStyle);
loginSignupTabs.addTab("Login", loginIcon, loginContainer);
}
What do I need to changenter code heree to get the elements to:
1. expand to the maximum width (no free space on the right)
2. fit within the visual area (for top-to-bottom)
Please note that I'm coding the elements because I find the (new) GUI Builder quite a challenge to use.
Firstly, don't pass a constant value as an argument to Layouts, coz the values might change in future Codename One updates and this will be difficult for you to debug. new BoxLayout(0) should be new BoxLayout(BoxLayout.Y_AXIS) or simply BoxLayout.y().
The above is where the problem arose but not the only problem because BoxLayout doesn't recognize 0 as a valid argument as it has only 3 which are X_AXIS = 1, Y_AXIS = 2, and X_AXIS_NO_GROW = 3.
If you change the above to use BoxLayout.Y_AXIS, it will work, but from the screenshot above, that's not the best solution.
In conclusion, change your code to below:
private final void show() {
loginSignupForm = new Form("Company", new BorderLayout());
Tabs loginSignupTabs = new Tabs();
Style loginSignupStyle = UIManager.getInstance().getComponentStyle("Tab");
prepareAndAddSignupTab(loginSignupTabs, loginSignupStyle);
prepareAndAddLoginTab(loginSignupTabs, loginSignupStyle);
loginSignupForm.add(BorderLayout.CENTER, loginSignupTabs);
loginSignupForm.show();
}
I am getting my feet wet with Codename One. I have looked into more other options like Xamarin, PhoneGap, Ionic for cross platform but I kinda got hooked with Codename one as it really code once and run anywhere.
I've been going through ui elements and I am kinda blocked on populating a combobox (Alternative is Picker)
Let's say I have stores as value pair (storeId, storeName). I want to display the storeName in Picker but keep storeId as the value reference.
Once the store is selected I would like to pass the storeId to an API call.
Is this possible. This might be very simple question but seems bit difficult to implement (I am really new to mobile).
Thank you.
Our recommendation is to avoid ComboBox. It's a UI pattern that doesn't exist on iOS natively and would feel alien on modern phones. It exists in Codename One.
In this code from the sample above you can get a similar effect to a complex multi-field combo box:
Form hi = new Form("Button", BoxLayout.y());
String[] characters = { "Tyrion Lannister", "Jaime Lannister", "Cersei Lannister"};
String[] actors = { "Peter Dinklage", "Nikolaj Coster-Waldau", "Lena Headey"};
int size = Display.getInstance().convertToPixels(7);
EncodedImage placeholder = EncodedImage.createFromImage(Image.createImage(size, size, 0xffcccccc), true);
Image[] pictures = {
URLImage.createToStorage(placeholder, "tyrion","http://i.lv3.hbo.com/assets/images/series/game-of-thrones/character/s5/tyrion-lannister-512x512.jpg"),
URLImage.createToStorage(placeholder, "jaime","http://i.lv3.hbo.com/assets/images/series/game-of-thrones/character/s5/jamie-lannister-512x512.jpg"),
URLImage.createToStorage(placeholder, "cersei","http://i.lv3.hbo.com/assets/images/series/game-of-thrones/character/s5/cersei-lannister-512x512.jpg")
};
MultiButton b = new MultiButton("Pick A Lanister...");
b.addActionListener(e -> {
Dialog d = new Dialog();
d.setLayout(BoxLayout.y());
d.getContentPane().setScrollableY(true);
for(int iter = 0 ; iter < characters.length ; iter++) {
MultiButton mb = new MultiButton(characters[iter]);
mb.setTextLine2(actors[iter]);
mb.setIcon(pictures[iter]);
d.add(mb);
mb.addActionListener(ee -> {
b.setTextLine1(mb.getTextLine1());
b.setTextLine2(mb.getTextLine2());
b.setIcon(mb.getIcon());
d.dispose();
b.revalidate();
});
}
d.showPopupDialog(b);
});
hi.add(b);
hi.show();
If you insist on using a ComboBox you can use a model to give it any object data you want. Then create a cell render to display the data. This is all discussed in depth in the component section of Codname One's developer guide. Notice that since ComboBox derives from List a lot of the List tips and docs apply to ComboBox.
I am using Selenium with Firefox Webdriver to work with elements on a page that has unique
CSS IDs (on every page load) but the IDs change every time so I am unable to use them to locate an element. This is because the page is a web application built with ExtJS.
I am trying to use Firebug to get the element information.
I need to find a unique xPath or selector so I can select each element individually with Selenium.
When I use Firebug to copy the xPath I get a value like this:
//*[#id="ext-gen1302"]
However, the next time the page is loaded it looks like this:
//*[#id="ext-gen1595"]
On that page every element has this ID format, so the CSS ID can not be used to find the element.
I want to get the xPath that is in terms of its position in the DOM, but Firebug will only return the ID xPath since it is unique for that instance of the page.
/html/body/div[4]/div[3]/div[4]/div/div/div/span[2]/span
How can I get Firebug (or another tool that would work with similar speed) to give me a unique selector that can be used to find the element with Selenium even after the ext-gen ID changes?
Another victim of ExtJS UI automation testing, here are my tips specifically for testing ExtJS. (However, this won't answer the question described in your title)
Tip 1: Don't ever use unreadable XPath like /div[4]/div[3]/div[4]/div/div/div/span[2]/span. One tiny change of source code may lead to DOM structure change. This will cause huge maintenance costs.
Tip 2: Take advantage of meaningful auto-generated partial ids and class names.
For example, this ExtJS grid example: By.cssSelector(".x-grid-view .x-grid-table") would be handy. If there are multiple of grids, try index them or locate the identifiable ancestor, like By.cssSelector("#something-meaningful .x-grid-view .x-grid-table").
Tip 3: Create meaningful class names in the source code. ExtJS provides cls and tdCls for custom class names, so you can add cls:'testing-btn-cancel' in your source code, and get it by By.cssSelector(".testing-btn-cancel").
Tip3 is the best and the final one. If you don't have access the source code, talk to your manager, Selenium UI automation should really be a developer job for someone who can modify the source code, rather than a end-user-like tester.
I would recommend using CSS in this instance by doing By.cssSelector("span[id^='ext-gen'])
The above statement means "select a span element with an id that starts with ext-gen". (If it needs to be more specific, you can reply, and I'll see if I can help you).
Alternatively, if you want to use XPath, look at this answer: Xpath for selecting html id including random number
Although it is not desired in some cases as mentioned above, you can parse through the elements and generate xpath ids.
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
public class XPATHDriverWrapper {
Map xpathIDToWebElementMap = new LinkedHashMap();
Map webElementToXPATHIDMap = new LinkedHashMap();
public XPATHDriverWrapper(WebDriver driver){
WebElement htmlElement = driver.findElement(By.xpath("/html"));
iterateThroughChildren(htmlElement, "/html");
}
private void iterateThroughChildren(WebElement parentElement, String parentXPATH) {
Map siblingCountMap = new LinkedHashMap();
List childrenElements = parentElement.findElements(By.xpath(parentXPATH+"/*"));
for(int i=0;i<childrenElements.size(); i++) {
WebElement childElement = childrenElements.get(i);
String childTag = childElement.getTagName();
String childXPATH = constructXPATH(parentXPATH, siblingCountMap, childTag);
xpathIDToWebElementMap.put(childXPATH, childElement);
webElementToXPATHIDMap.put(childElement, childXPATH);
iterateThroughChildren(childElement, childXPATH);
// System.out.println("childXPATH:"+childXPATH);
}
}
public WebElement findWebElementFromXPATHID(String xpathID) {
return xpathIDToWebElementMap.get(xpathID);
}
public String findXPATHIDFromWebElement(WebElement webElement) {
return webElementToXPATHIDMap.get(webElement);
}
private String constructXPATH(String parentXPATH,
Map siblingCountMap, String childTag) {
Integer count = siblingCountMap.get(childTag);
if(count == null) {
count = 1;
} else {
count = count + 1;
}
siblingCountMap.put(childTag, count);
String childXPATH = parentXPATH + "/" + childTag + "[" + count + "]";
return childXPATH;
}
}
Another wrapper to generate ids from Document is posted at: http://scottizu.wordpress.com/2014/05/12/generating-unique-ids-for-webelements-via-xpath/