For example, how to find the following Text with identifier "id_d".
struct a : View {
var body : some View {
VStack {
HStack {
Text("b")
Image(....)
}
HStack {
....
TextField(...)
Text("d").accessibleIdentifier("id_d")
}
}
}
}
Backgound:
I have a requirement that implementing a coach marks (or walk through) in existing project written in swiftui. in which there are server View files, so I just want to find a common way to achieve this function instead of adding modifiers with every element. in common process, i need to find target by identifier in runtime, and get the position of it, and add coach marks using other view.
Related
I am implementing a category mapper. There are 2 TreeViews. Both contain categories from different sources. (Even they look like from the same source)
The user should be able to map ONE category from the left to multiple of the right treeview. This gets stored in a config file.
However, when the view is initially loaded and the user clicks on a category on the left, I want to preselect the mapped categories on the right, loaded from the config file.
I saw that I can do this with ONE selection, but I don't see an option to do this for multiple ones.
How can I achive this?
Here a ootb running demo implementation
I went through your gist and it seemed that the problem was binding selections right? I did some light digging and found that binding observable lists isn't easy. I don't think I even saw a solution that wasn't just adding listeners to mock a binding. However, wrapping that list in a SimpleListProperty seemed to do the trick. Here's a demo:
class TestView : View() {
// This mocks your target list in the ViewModel
val targetList = SimpleListProperty(listOf<String>().asObservable())
override val root = vbox(10) {
// Both table views and tree views use an Observable Selection list
listview(listOf("asefas", "asefasef", "asefasefasefase").asObservable()) {
// Wrap in SimpleListProperty, then bind
targetList.bind(SimpleListProperty(selectionModel.selectedItems))
selectionModel.selectionMode = SelectionMode.MULTIPLE
}
setPrefSize(300.0, 300.0)
}
init {
// Target list now reflects changes made to selection model
targetList.addListener { _, _, change ->
println("Selections changed to: $change")
}
}
}
I am trying to provide an accessibility label for UIActivityIndicatorView (which is created programmatically in my view controllers viewDidLoad). I am setting the accessibility label as:
myIndicatorView.accessibilityLabel = #"Please wait, processing"
But when I run the application, the voice over always reads "in progress". I tried to debug on simulator using the accessibility inspector, but everytime the indicator view is in focus, it has the label as "in progress". I assume, "in progress" is default voice over text for activity indicators view, But I can not change this label. I am wondering if the activity indicator view's accessble label can never be changed.
If somebody came across this issue and found a workaround, then please help me.
It's not that you're not changing it. It's that, in the background, as the status of the progress indicator changes, iOS backend updates the label, to the appropriate status. This is overriding whatever you changed it to, because it is likely applying its own update after you change the status.
I would just leave this alone. "Please wait, processing" provides no additional information as compared to "In progress". And "In progress" is the way VoiceOver users will be accustomed to hearing an "In progress" state progress indicator announce. Changing this announcement is to a non-sighted user, what changing the image to a revolving Mickey Mouse head would be to sighted one.
If you MUST change this, what you want to do, is instead of setting the property, override the implementation of the property's getter method. To do this provide a custom implementation of UIActivityIndicatorView that does the following.
#interface MyActivityIndicator : UIActivityIndicatorView
#end
#implementation MYActivityIndicator
- (NSString*)accessibilityLabel {
if (self.isAnimating) {
return NSLocalizedString("ACTIVITY_INDICATOR_ACTIVE", nil);
} else {
return NSLocalizedString("ACTIVITY_INDICATOR_INACTIVE", nil);
}
}
UIActivityIndicatorView subclass in Swift
The implementation of the accessibilityLabel getter in UIActivityIndicatorView is dynamic based on the state of the control. Therefore, if you set its accessibilityLabel, it may change later.
The following UIActivityIndicatorView subclass overrides the default implementation of accessibilityLabel. It is based on the answer by #ChrisCM in Objective C.
class MyActivityIndicatorView: UIActivityIndicatorView {
override var accessibilityLabel: String? {
get {
if isAnimating {
return NSLocalizedString("ACTIVITY_INDICATOR_ACTIVE", comment: "");
}
else {
return NSLocalizedString("ACTIVITY_INDICATOR_INACTIVE", comment: "");
}
}
set {
super.accessibilityLabel = newValue
}
}
}
In my app, the activity indicator is visible on the screen and to VoiceOver only when it is animating. Therefore, I only need one accessibilityLabel value. The following subclass uses the default, dynamic implementation of accessibilityLabel unless set explicitly. If set, it uses that value regardless of the state.
class MyActivityIndicatorView: UIActivityIndicatorView {
private var accessibilityLabelOverride: String?
override var accessibilityLabel: String? {
get {
if accessibilityLabelOverride != nil {
return accessibilityLabelOverride
}
return super.accessibilityLabel
}
set {
accessibilityLabelOverride = newValue
}
}
}
// Example use
let activityIndicatorView = MyActivityIndicatorView(activityIndicatorStyle: .gray)
activityIndicatorView.accessibilityLabel = NSLocalizedString("ACTIVITY_INDICATOR", comment: "")
So here's my screnario. I have a toolbar at the top (office style), with buttons. This is hosted in a shell. Some of those buttons are applicable only to certain child view models as they get loaded. Ideally what I would like to happen is have the buttons action.target repositioned to child view model as it gets created (I kind of got this working by settings Action.Target="ActiveItem" on them. This doesn't solve the problem fully though:
a) When the child viewmodel is closed and there is no active item, I want them to reposition to Shell as the target so they can be set to "default" state.
b) I noticed that when child viewmodel is closed and the shell being the conductor has it ActiveItem=null, the hooks from the action are still bound to the living instance of the last viewmodel, so doesn't looks like it got disposed of. Memory leak?
Any suggestions how to implement this scenario?
What about adding a property to your ShellViewModel which points to the action target and updating it when stuff gets activated/deactivated:
e.g.
public class ShellViewModel
{
public object ActionTarget
{
get { return _actionTarget; }
set
{
_actionTarget = value;
NotifyOfPropertyChange(() => ActionTarget);
}
}
// Then when the active item changes just update the target:
public override NotifyOfPropertyChange(string propertyName)
{
if(propertyName == "ActiveItem")
{
if(ActiveItem == null) ActionTarget = this;
else ActionTarget = ActiveItem;
}
}
}
Now bind to that:
<SomeMenu cal:Action.Target="{Binding ActionTarget}" />
Not sure if that will work or not but I'm sure I've done something similar in the past. (You may also have to explicitly call NPC on your actions before they will update after you have changed ActiveItem)
The problem:
.Click()-ing a radio button webdriver element stales it(no control over the page that does this). The DOM element itself is still the same.
The goal:
I want to reset the existing webdriver element using its own original selector method so that it is no longer stale. I want a general solution that does not require knowing ahead of time how the element was found. I want to use the existing stale element to do the work. Ideal case would look something like this(using the following C# extension method just for sake of example):
IWebElement refreshedElement = driver.FindElement(staleElement.By());
The question:
Is there a way to expose the existing elements location? Is the 'address' of the element available anywhere? It doesn't even have to be the original method of addressing the element when it was found, I don't care about that. I'd just rather not have to make a subclass just to capture this information.
No, Selenium does not keep track of 'how' you found an element, and frankly I don't think that should be Selenium's responsibility.
I would wrap it into a new class, which inherits from RemoteWebElement, and has a method called RefindElement.
I might suggest considering adding the concept of a "Page Class". Basically, instead of adding the element to the test itself, I create a separate class that has methods that return elements.
For example, a login page would have three elements therefore 3 methods:
public class LoginPage
{
private IWebDriver driver { get; set; }
public CSCView_SalesAspx(IWebDriver driver) { this.driver = driver; }
public IWebElement Id { get { return driver.FindElement(By.Id("login_id")); } }
public IWebElement Pw { get { return driver.FindElement(By.Id("login_pw")); } }
public IWebElement SubmitBtn { get { return driver.FindElement(By.Id("submitBtn")); } }
}
Now all you have to do is instantiate the class then interact with the method. Your element should always be "fresh" since you're doing the lookup every time (without any extra work).
LoginPage loginPage = new LoginPage(driver);
loginPage.Id.SendKeys("myName");
loginPage.Pw.SendKeys("myPw");
loginPage.SubmitBtn.Click();
The best thing about this is if a page changes, instead of having to rewrite every test, you only change one page class and that fixes your broken tests.
I've found it's often useful to special case the first item in a drop-down menu (ie, an instance of Menu). For example, if I want to pick a color from the list provided by a web service:
<mx:PopUpMenuButton id="colorSelelector"
dataProvider="{colorsService.lastResult}" />
I might also want a special-case, which is "enter a new color", allowing the user to enter the RGB values for a new color which isn't in the list. For example:
var newColor = { label: "Enter a new color", rgb: null };
Then used with:
<mx:PopUpMenuButton id="colorSelelector"
dataProvider="{colorsService.lastResult}"
lastOption="{newColor}" />
So, apart from changing the list I get back from the service, is there any better way to do this?
(and just a preemptive comment: this is a simplification… I'm not actually trying to make a color-picking-list)
When you bind to the dataProvider, call a function that adds your special case. For instance:
<mx:PopUpMenuButton id="colorSelector"
dataProvider="{addSpecialCases(colorsService.lastResult)}"/>
So, apart from changing the list I get
back from the service, is there any
better way to do this?
This approach is going to be the cleanest, without extending HTTPService, which would work well (but is really just altering your result ;) ):
package
{
import mx.rpc.http.HTTPService;
public class MyHTTPService extends HTTPService
{
public var appendToResult:Object;
public function MyHTTPService(rootURL:String=null, destination:String=null)
{
super(rootURL, destination);
}
[Bindable("resultForBinding")]
override public function get lastResult():Object
{
//I know what my type is, Array as an example
var myResult:Array = operation.lastResult;
myResult.push( this.appendToResult )
return myResult;
}
}
}